普通视图

发现新文章,点击刷新页面。
今天 — 2025年4月11日LeetCode 每日一题题解

[Python3/Java/C++/Go/TypeScript] 一题一解:枚举(清晰题解)

作者 lcbin
2025年4月11日 06:24

方法一:枚举

我们枚举 $[low, high]$ 中的每个整数 $x$,判断其是否是对称整数。如果是,那么答案 $ans$ 增加 $1$。

###python

class Solution:
    def countSymmetricIntegers(self, low: int, high: int) -> int:
        def f(x: int) -> bool:
            s = str(x)
            if len(s) & 1:
                return False
            n = len(s) // 2
            return sum(map(int, s[:n])) == sum(map(int, s[n:]))

        return sum(f(x) for x in range(low, high + 1))

###java

class Solution {
    public int countSymmetricIntegers(int low, int high) {
        int ans = 0;
        for (int x = low; x <= high; ++x) {
            ans += f(x);
        }
        return ans;
    }

    private int f(int x) {
        String s = "" + x;
        int n = s.length();
        if (n % 2 == 1) {
            return 0;
        }
        int a = 0, b = 0;
        for (int i = 0; i < n / 2; ++i) {
            a += s.charAt(i) - '0';
        }
        for (int i = n / 2; i < n; ++i) {
            b += s.charAt(i) - '0';
        }
        return a == b ? 1 : 0;
    }
}

###cpp

class Solution {
public:
    int countSymmetricIntegers(int low, int high) {
        int ans = 0;
        auto f = [](int x) {
            string s = to_string(x);
            int n = s.size();
            if (n & 1) {
                return 0;
            }
            int a = 0, b = 0;
            for (int i = 0; i < n / 2; ++i) {
                a += s[i] - '0';
                b += s[n / 2 + i] - '0';
            }
            return a == b ? 1 : 0;
        };
        for (int x = low; x <= high; ++x) {
            ans += f(x);
        }
        return ans;
    }
};

###go

func countSymmetricIntegers(low int, high int) (ans int) {
f := func(x int) int {
s := strconv.Itoa(x)
n := len(s)
if n&1 == 1 {
return 0
}
a, b := 0, 0
for i := 0; i < n/2; i++ {
a += int(s[i] - '0')
b += int(s[n/2+i] - '0')
}
if a == b {
return 1
}
return 0
}
for x := low; x <= high; x++ {
ans += f(x)
}
return
}

###ts

function countSymmetricIntegers(low: number, high: number): number {
    let ans = 0;
    const f = (x: number): number => {
        const s = x.toString();
        const n = s.length;
        if (n & 1) {
            return 0;
        }
        let a = 0;
        let b = 0;
        for (let i = 0; i < n >> 1; ++i) {
            a += Number(s[i]);
            b += Number(s[(n >> 1) + i]);
        }
        return a === b ? 1 : 0;
    };
    for (let x = low; x <= high; ++x) {
        ans += f(x);
    }
    return ans;
}

时间复杂度 $O(n \times \log m)$,空间复杂度 $O(\log m)$。其中 $n$ 是 $[low, high]$ 中整数的个数,而 $m$ 是题目中给出的最大整数。


有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~

每日一题-统计对称整数的数目🟢

2025年4月11日 00:00

给你两个正整数 lowhigh

对于一个由 2 * n 位数字组成的整数 x ,如果其前 n 位数字之和与后 n 位数字之和相等,则认为这个数字是一个对称整数。

返回在 [low, high] 范围内的 对称整数的数目

 

示例 1:

输入:low = 1, high = 100
输出:9
解释:在 1 到 100 范围内共有 9 个对称整数:11、22、33、44、55、66、77、88 和 99 。

示例 2:

输入:low = 1200, high = 1230
输出:4
解释:在 1200 到 1230 范围内共有 4 个对称整数:1203、1212、1221 和 1230 。

 

提示:

  • 1 <= low <= high <= 104

记忆化数位DP

2023年9月3日 21:05

Problem: https://leetcode.cn/problems/count-symmetric-integers/description/

image.png

解题方法

用记忆化的方法保存以数字之和或之差为$sum$开始,在长度为$index + 1$的整数中所有对称整数的数目。以下是代码中用到的其它各个变量的含义。

  • $index$ 表示目前正在处理数中的第几个十进制位。我们从数的最高位开始处理。$index + 1$ 则表示目前还要处理多少个数字。

  • $len$ 目前正在构建的非$0$整数的长度

  • $sum$ 逐位加上前一半部分的各位数字和减去后一半部分的各位数字。

  • $isLeadingZero$ 表示前面高位的数字是否都为 $0$。

  • $isMaxDigit$ 表示前面高位的数字是否都为最大值。

Code

###Java


class Solution {
    static int[][] memo = new int[5][19];
    static int[] digits = new int[5];

    public int countSymmetricIntegers(int low, int high) {
        return calc(high) - calc(low - 1);
    }

    int calc(int num) {
        if (num == 0) return 0;
       
        int n = 0;
        while (num > 0) {
            digits[n++] = num % 10;
            num /= 10;
        }
        return dfs(n - 1, 0, 0, true, true);
    }       
    
    int dfs(int index, int len, int sum, boolean isLeadingZero, boolean isMaxDigit) {

        if (index == -1) {
            if ((len & 1) == 0 && sum == 0 && !isLeadingZero) return 1;
            return 0;
        }
        
        if(!isLeadingZero && ((len + index ) & 1) == 0)
            return 0;
   
        if(!isLeadingZero && !isMaxDigit && memo[index][sum] != 0)
            return memo[index][sum];
            
        int result = 0;
        int maxDigit = isMaxDigit? digits[index] : 9;
        for (int digit = 0; digit <= maxDigit; ++digit) {
            
            int newLen = isLeadingZero && digit == 0 ? 0:len + 1;
            int newSum = len < index? sum + digit:sum - digit;
            if(newSum < 0) break;
            
            result += dfs(index - 1, newLen, newSum, isLeadingZero && digit == 0, isMaxDigit && digit == maxDigit);
        }

        if(!isLeadingZero && !isMaxDigit)
            memo[index][sum] = result;     
        return result;
    }
}


类似的方法可以用来解题1012. 至少有 1 位重复的数字

image.png

Code

###Java


class Solution {
    static int[][] memo = new int[10][1 << 10];
    static int[] digits = new int[10];
  
    public int numDupDigitsAtMostN(int n) {
        return n - calculate(n);
    }
  
    int calculate(int n) {
        int size = 0;
      
        while (n > 0) {
            digits[size++] = n % 10;
            n /= 10;
        }
        
        return dfs(size - 1, 0, true, true);
    }

    int dfs(int index, int mask, boolean isLeadingZero, boolean isMaxDigit) { 
    
        if (index == -1) 
            return  isLeadingZero? 0 : 1;
        
        if (!isLeadingZero && !isMaxDigit && memo[index][mask] != 0) 
            return memo[index][mask];
        
        int result = 0;
        int maxDigit = isMaxDigit ? digits[index] : 9;
        
        for (int i = 0; i <= maxDigit; i++) {
            if ((mask & (1 << i)) != 0) continue;
            int newMask = isLeadingZero && (i == 0) ? mask : mask| (1 << i); 
            result += dfs(index - 1, newMask,  isLeadingZero && i == 0, isMaxDigit && (i == maxDigit));
        }
        
        if (!isLeadingZero && !isMaxDigit) 
            memo[index][mask] = result;
        
        return result;
    }
}


双指针解决对称整数问题

2023年9月3日 14:14

Problem: 7020. 统计对称整数的数目

[TOC]

思路

转换成字符串string,然后定义left,right指针,相向而行,依次存储数值,最后对比s左 s右的数值是否相等。

解题方法

双指针算法

复杂度

  • 时间复杂度:

添加时间复杂度, 示例: $O(n)$

Code

###C++


class Solution 
{
    int ans = 0;
public:
    int countSymmetricIntegers(int low, int high) 
    {
        for(int i = low;i <= high; i++)
        {
            int j = i;
            if(i < 10) continue;
            string s = to_string(j);
            if(s.size()%2 == 1) continue; //101,121这种情况直接跳过
            int left = 0,right = s.size() - 1;
            int s1 = 0,s2 = 0;
            while(left < right)
            {
                s1 += s[left++] - '0';
                s2 += s[right--] - '0';
            }
            if(s1 == s2) ans++;
            else continue;
        }    
        return ans;
    }
};

两种方法:枚举 / 数位 DP(Python/Java/C++/Go)

作者 endlesscheng
2023年9月3日 12:21

方法一:枚举

枚举 $[\textit{low},\textit{high}]$ 中的整数 $i$。

设 $i$ 的十进制字符串为 $s$。如果 $s$ 的长度为偶数,且 $s$ 左半字符 ASCII 值之和等于 $s$ 右半字符 ASCII 值之和,那么 $i$ 是一个对称整数,答案加一。

class Solution:
    def countSymmetricIntegers(self, low: int, high: int) -> int:
        ans = 0
        for i in range(low, high + 1):
            s = str(i)
            n = len(s)
            if n % 2 == 0 and sum(map(ord, s[:n // 2])) == sum(map(ord, s[n // 2:])):
                ans += 1
        return ans
class Solution {
    public int countSymmetricIntegers(int low, int high) {
        int ans = 0;
        for (int i = low; i <= high; i++) {
            char[] s = Integer.toString(i).toCharArray();
            int n = s.length;
            if (n % 2 > 0) {
                continue;
            }
            int diff = 0;
            for (int j = 0; j < n / 2; j++) {
                diff += s[j];
            }
            for (int j = n / 2; j < n; j++) {
                diff -= s[j];
            }
            if (diff == 0) {
                ans++;
            }
        }
        return ans;
    }
}
class Solution {
public:
    int countSymmetricIntegers(int low, int high) {
        int ans = 0;
        for (int i = low; i <= high; i++) {
            string s = to_string(i);
            int n = s.size();
            if (n % 2 == 0 && reduce(s.begin(), s.begin() + n / 2) == reduce(s.begin() + n / 2, s.end())) {
                ans++;
            }
        }
        return ans;
    }
};
func countSymmetricIntegers(low, high int) (ans int) {
    for i := low; i <= high; i++ {
        s := strconv.Itoa(i)
        n := len(s)
        if n%2 > 0 {
            continue
        }
        diff := 0
        for _, c := range s[:n/2] {
            diff += int(c)
        }
        for _, c := range s[n/2:] {
            diff -= int(c)
        }
        if diff == 0 {
            ans++
        }
    }
    return
}

复杂度分析

  • 时间复杂度:$\mathcal{O}((\textit{high} - \textit{low})\log \textit{high})$。
  • 空间复杂度:$\mathcal{O}(\log \textit{high})$。

方法二:上下界数位 DP

如果 $\textit{high} = 10^{18}$,方法一就超时了,怎么办?

前置知识

数位 DP v1.0 视频讲解

数位 DP v2.0 视频讲解(上下界数位 DP)

状态定义:$\textit{dfs}(i, \textit{start}, \textit{diff},\textit{limitLow},\textit{limitHigh})$ 表示构造第 $i$ 位及其之后数位的合法方案数,其余参数的含义为:

  • $\textit{start}$ 表示从左往右第一个非零数字的下标。用于判断现在处于对称整数的左半还是右半。
  • $\textit{diff}$ 表示左半数字与右半数字之差。如果最终 $\textit{diff}=0$,则说明我们成功构造出一个对称整数。
  • $\textit{limitHigh}$ 表示当前是否受到了 $\textit{high}$ 的约束(我们要构造的数字不能超过 $\textit{high}$)。若为真,则第 $i$ 位填入的数字至多为 $\textit{high}[i]$,否则至多为 $9$,这个数记作 $\textit{hi}$。如果在受到约束的情况下填了 $\textit{hi}$,那么后续填入的数字仍会受到 $\textit{high}$ 的约束。例如 $\textit{high}=123$,那么 $i=0$ 填的是 $1$ 的话,$i=1$ 的这一位至多填 $2$。
  • $\textit{limitLow}$ 表示当前是否受到了 $\textit{low}$ 的约束(我们要构造的数字不能低于 $\textit{low}$)。若为真,则第 $i$ 位填入的数字至少为 $\textit{low}[i]$,否则至少为 $0$,这个数记作 $\textit{lo}$。如果在受到约束的情况下填了 $\textit{lo}$,那么后续填入的数字仍会受到 $\textit{low}$ 的约束。

状态转移

  • 如果前面没有填数字,且剩余数位个数是奇数,那么当前数位不能填数字(因为对称整数的长度必须是偶数),往后递归。在这种情况下,如果 $\textit{lo}>0$,那么必须填数字,但这不合法,直接返回 $0$。
  • 否则,枚举第 $i$ 位填数字 $d=\textit{lo},\textit{lo}+1,\ldots,\textit{hi}$。如果之前没有填过数字,且当前填的数字大于 $0$,那么记录 $\textit{start}=i$。如果 $i< \dfrac{\textit{start}+n}{2}$,说明我们在左半,把 $\textit{diff}$ 增加 $d$,否则把 $\textit{diff}$ 减少 $d$。

递归终点:$i=n$ 时,如果 $\textit{diff}=0$,说明我们成功构造出一个对称整数,返回 $1$,否则返回 $0$。

递归入口:$\textit{dfs}(0, -1, 0, \texttt{true}, \texttt{true})$,表示:

  • 从最高位开始。
  • 一开始没有填任何数字。
  • 一开始要受到 $\textit{low}$ 和 $\textit{high}$ 的约束(否则就可以随意填了,这肯定不行)。
class Solution:
    def countSymmetricIntegers(self, low: int, high: int) -> int:
        high = list(map(int, str(high)))  # 避免在 dfs 中频繁调用 int()
        n = len(high)
        low = list(map(int, str(low).zfill(n)))  # 补前导零,和 high 对齐

        @cache
        def dfs(i: int, start: int, diff: int, limit_low: bool, limit_high: bool) -> int:
            if i == n:
                return 1 if diff == 0 else 0

            lo = low[i] if limit_low else 0
            hi = high[i] if limit_high else 9

            # 如果前面没有填数字,且剩余数位个数是奇数,那么当前数位不能填数字
            if start < 0 and (n - i) % 2:
                # 如果必须填数字(lo > 0),不合法,返回 0
                return 0 if lo else dfs(i + 1, start, diff, True, False)

            res = 0
            is_left = start < 0 or i < (start + n) // 2
            for d in range(lo, hi + 1):
                res += dfs(i + 1,
                           i if start < 0 and d else start,  # 记录第一个填数字的位置
                           diff + (d if is_left else -d),  # 左半 + 右半 -
                           limit_low and d == lo,
                           limit_high and d == hi)
            return res

        return dfs(0, -1, 0, True, True)
class Solution {
    private char[] lowS, highS;
    private int n, m, diffLh;
    private int[][][] memo;

    public int countSymmetricIntegers(int low, int high) {
        lowS = String.valueOf(low).toCharArray();
        highS = String.valueOf(high).toCharArray();
        n = highS.length;
        m = n / 2;
        diffLh = n - lowS.length;

        memo = new int[n][diffLh + 1][m * 18 + 1]; // 注意 start <= diffLh
        for (int[][] mat : memo) {
            for (int[] row : mat) {
                Arrays.fill(row, -1);
            }
        }

        // 初始化 diff = m * 9,避免出现负数导致 memo 下标越界
        return dfs(0, -1, m * 9, true, true);
    }

    private int dfs(int i, int start, int diff, boolean limitLow, boolean limitHigh) {
        if (i == n) {
            return diff == m * 9 ? 1 : 0;
        }

        // start 当 isNum 用
        if (start != -1 && !limitLow && !limitHigh && memo[i][start][diff] != -1) {
            return memo[i][start][diff];
        }

        int lo = limitLow && i >= diffLh ? lowS[i - diffLh] - '0' : 0;
        int hi = limitHigh ? highS[i] - '0' : 9;

        // 如果前面没有填数字,且剩余数位个数是奇数,那么当前数位不能填数字
        if (start < 0 && (n - i) % 2 > 0) {
            // 如果必须填数字(lo > 0),不合法,返回 0
            return lo > 0 ? 0 : dfs(i + 1, start, diff, true, false);
        }

        int res = 0;
        boolean isLeft = start < 0 || i < (start + n) / 2;
        for (int d = lo; d <= hi; d++) {
            res += dfs(i + 1,
                       start < 0 && d > 0 ? i : start, // 记录第一个填数字的位置
                       diff + (isLeft ? d : -d), // 左半 +,右半 -
                       limitLow && d == lo,
                       limitHigh && d == hi);
        }

        if (start != -1 && !limitLow && !limitHigh) {
            memo[i][start][diff] = res;
        }
        return res;
    }
}
class Solution {
public:
    int countSymmetricIntegers(int low, int high) {
        string low_s = to_string(low), high_s = to_string(high);
        int n = high_s.size(), m = n / 2;
        int diff_lh = n - low_s.size();

        vector memo(n, vector(diff_lh + 1, vector<int>(m * 18 + 1, -1))); // 注意 start <= diff_lh
        auto dfs = [&](this auto&& dfs, int i, int start, int diff, bool limit_low, bool limit_high) -> int {
            if (i == n) {
                return diff == m * 9;
            }

            // start 当 is_num 用
            if (start != -1 && !limit_low && !limit_high && memo[i][start][diff] != -1) {
                return memo[i][start][diff];
            }

            int lo = limit_low && i >= diff_lh ? low_s[i - diff_lh] - '0' : 0;
            int hi = limit_high ? high_s[i] - '0' : 9;

            // 如果前面没有填数字,且剩余数位个数是奇数,那么当前数位不能填数字
            if (start < 0 && (n - i) % 2) {
                // 如果必须填数字(lo > 0),不合法,返回 0
                return lo > 0 ? 0 : dfs(i + 1, start, diff, true, false);
            }

            int res = 0;
            bool is_left = start < 0 || i < (start + n) / 2;
            for (int d = lo; d <= hi; d++) {
                res += dfs(i + 1,
                           start < 0 && d > 0 ? i : start, // 记录第一个填数字的位置
                           diff + (is_left ? d : -d), // 左半 +,右半 -
                           limit_low && d == lo,
                           limit_high && d == hi);
            }

            if (start != -1 && !limit_low && !limit_high) {
                memo[i][start][diff] = res;
            }
            return res;
        };

        // 初始化 diff = m * 9,避免出现负数导致 memo 下标越界
        return dfs(0, -1, m * 9, true, true);
    }
};
func countSymmetricIntegers(low, high int) int {
    lowS := strconv.Itoa(low)
    highS := strconv.Itoa(high)
    n := len(highS)
    m := n / 2
    diffLH := n - len(lowS)

    memo := make([][][]int, n)
    for i := range memo {
        memo[i] = make([][]int, diffLH+1) // start <= diffLH
        for j := range memo[i] {
            memo[i][j] = make([]int, m*18+1)
            for k := range memo[i][j] {
                memo[i][j][k] = -1
            }
        }
    }
    var dfs func(int, int, int, bool, bool) int
    dfs = func(i, start, diff int, limitLow, limitHigh bool) (res int) {
        if i == n {
            if diff != 0 {
                return 0
            }
            return 1
        }

        // start 当 isNum 用
        if start != -1 && !limitLow && !limitHigh {
            p := &memo[i][start][diff+m*9]
            if *p != -1 {
                return *p
            }
            defer func() { *p = res }()
        }

        lo := 0
        if limitLow && i >= diffLH {
            lo = int(lowS[i-diffLH] - '0')
        }
        hi := 9
        if limitHigh {
            hi = int(highS[i] - '0')
        }

        // 如果前面没有填数字,且剩余数位个数是奇数,那么当前数位不能填数字
        if start < 0 && (n-i)%2 > 0 {
            if lo > 0 {
                return 0 // 必须填数字但 lo > 0,不合法
            }
            return dfs(i+1, start, diff, true, false)
        }

        isLeft := start < 0 || i < (start+n)/2
        for d := lo; d <= hi; d++ {
            newStart := start
            if start < 0 && d > 0 {
                newStart = i // 记录第一个填数字的位置
            }
            newDiff := diff
            if isLeft {
                newDiff += d
            } else {
                newDiff -= d
            }
            res += dfs(i+1, newStart, newDiff, limitLow && d == lo, limitHigh && d == hi)
        }
        return
    }
    return dfs(0, -1, 0, true, true)
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(D^2n^3)$,其中 $D=10$,$n=\mathcal{O}(\log \textit{high})$。由于每个状态只会计算一次,动态规划的时间复杂度 $=$ 状态个数 $\times$ 单个状态的计算时间。本题 $i$ 有 $\mathcal{O}(n)$ 个,$\textit{start}$ 有 $\mathcal{O}(n)$ 个,$\textit{diff}$ 有 $\mathcal{O}(Dn)$ 个,所以状态个数为 $\mathcal{O}(Dn^3)$,单个状态的计算时间为 $\mathcal{O}(D)$,所以总的时间复杂度为 $\mathcal{O}(D^2n^3)$。
  • 空间复杂度:$\mathcal{O}(Dn^3)$。保存多少状态,就需要多少空间。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
  7. 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

昨天 — 2025年4月10日LeetCode 每日一题题解

[Python3/Java/C++/Go/TypeScript] 一题一解:数位 DP(清晰题解)

作者 lcbin
2025年4月10日 06:17

方法一:数位 DP

这道题实际上是求在给定区间 $[l,..r]$ 中,满足条件的数的个数。个数与数的位数以及每一位上的数字有关。我们可以用数位 DP 的思路来解决这道题。数位 DP 中,数的大小对复杂度的影响很小。

对于区间 $[l,..r]$ 问题,我们一般会将其转化为 $[1,..r]$ 然后再减去 $[1,..l - 1]$ 的问题,即:

$$
ans = \sum_{i=1}^{r} ans_i - \sum_{i=1}^{l-1} ans_i
$$

对于本题而言,我们求出 $[1, \textit{finish}]$ 中满足条件的数的个数,然后减去 $[1, \textit{start} - 1]$ 中满足条件的数的个数,即可得到答案。

这里我们用记忆化搜索来实现数位 DP。从起点向下搜索,到最底层得到方案数,一层层向上返回答案并累加,最后从搜索起点得到最终的答案。

基本步骤如下:

  1. 先将 $\textit{start}$ 和 $\textit{finish}$ 转化为字符串,方便后续的数位 DP。
  2. 设计一个函数 $\textit{dfs}(\textit{pos}, \textit{lim})$,表示从第 $\textit{pos}$ 位开始搜索,当前的限制条件为 $\textit{lim}$。
  3. 如果最大的数字位数小于 $\textit{s}$ 的长度,返回 0。
  4. 如果当前剩余的数字位数等于 $\textit{s}$ 的长度,判断当前的数字是否满足条件,返回 1 或 0。
  5. 否则,我们计算当前位的上限 $\textit{up} = \min(\textit{lim} ? \textit{t}[\textit{pos}] : 9, \textit{limit})$。然后遍历当前位的数字 $i$,从 0 到 $\textit{up}$,递归调用 $\textit{dfs}(\textit{pos} + 1, \textit{lim} && i == \textit{t}[\textit{pos}])$,将结果累加到答案中。
  6. 如果当前的 $\textit{lim}$ 为 false,则将当前的答案存入缓存中,避免重复计算。
  7. 最后返回答案。

答案为区间 $[1, \textit{finish}]$ 中满足条件的数的个数减去区间 $[1, \textit{start} - 1]$ 中满足条件的数的个数。

###python

class Solution:
    def numberOfPowerfulInt(self, start: int, finish: int, limit: int, s: str) -> int:
        @cache
        def dfs(pos: int, lim: int) -> int:
            if len(t) < n:
                return 0
            if len(t) - pos == n:
                return int(s <= t[pos:]) if lim else 1
            up = min(int(t[pos]) if lim else 9, limit)
            ans = 0
            for i in range(up + 1):
                ans += dfs(pos + 1, lim and i == int(t[pos]))
            return ans

        n = len(s)
        t = str(start - 1)
        a = dfs(0, True)
        dfs.cache_clear()
        t = str(finish)
        b = dfs(0, True)
        return b - a

###java

class Solution {
    private String s;
    private String t;
    private Long[] f;
    private int limit;

    public long numberOfPowerfulInt(long start, long finish, int limit, String s) {
        this.s = s;
        this.limit = limit;
        t = String.valueOf(start - 1);
        f = new Long[20];
        long a = dfs(0, true);
        t = String.valueOf(finish);
        f = new Long[20];
        long b = dfs(0, true);
        return b - a;
    }

    private long dfs(int pos, boolean lim) {
        if (t.length() < s.length()) {
            return 0;
        }
        if (!lim && f[pos] != null) {
            return f[pos];
        }
        if (t.length() - pos == s.length()) {
            return lim ? (s.compareTo(t.substring(pos)) <= 0 ? 1 : 0) : 1;
        }
        int up = lim ? t.charAt(pos) - '0' : 9;
        up = Math.min(up, limit);
        long ans = 0;
        for (int i = 0; i <= up; ++i) {
            ans += dfs(pos + 1, lim && i == (t.charAt(pos) - '0'));
        }
        if (!lim) {
            f[pos] = ans;
        }
        return ans;
    }
}

###cpp

class Solution {
public:
    long long numberOfPowerfulInt(long long start, long long finish, int limit, string s) {
        string t = to_string(start - 1);
        long long f[20];
        memset(f, -1, sizeof(f));

        auto dfs = [&](this auto&& dfs, int pos, int lim) -> long long {
            if (t.size() < s.size()) {
                return 0;
            }
            if (!lim && f[pos] != -1) {
                return f[pos];
            }
            if (t.size() - pos == s.size()) {
                return lim ? s <= t.substr(pos) : 1;
            }
            long long ans = 0;
            int up = min(lim ? t[pos] - '0' : 9, limit);
            for (int i = 0; i <= up; ++i) {
                ans += dfs(pos + 1, lim && i == (t[pos] - '0'));
            }
            if (!lim) {
                f[pos] = ans;
            }
            return ans;
        };

        long long a = dfs(0, true);
        t = to_string(finish);
        memset(f, -1, sizeof(f));
        long long b = dfs(0, true);
        return b - a;
    }
};

###go

func numberOfPowerfulInt(start, finish int64, limit int, s string) int64 {
t := strconv.FormatInt(start-1, 10)
f := make([]int64, 20)
for i := range f {
f[i] = -1
}

var dfs func(int, bool) int64
dfs = func(pos int, lim bool) int64 {
if len(t) < len(s) {
return 0
}
if !lim && f[pos] != -1 {
return f[pos]
}
if len(t)-pos == len(s) {
if lim {
if s <= t[pos:] {
return 1
}
return 0
}
return 1
}

ans := int64(0)
up := 9
if lim {
up = int(t[pos] - '0')
}
up = min(up, limit)
for i := 0; i <= up; i++ {
ans += dfs(pos+1, lim && i == int(t[pos]-'0'))
}
if !lim {
f[pos] = ans
}
return ans
}

a := dfs(0, true)
t = strconv.FormatInt(finish, 10)
for i := range f {
f[i] = -1
}
b := dfs(0, true)
return b - a
}

###ts

function numberOfPowerfulInt(start: number, finish: number, limit: number, s: string): number {
    let t: string = (start - 1).toString();
    let f: number[] = Array(20).fill(-1);

    const dfs = (pos: number, lim: boolean): number => {
        if (t.length < s.length) {
            return 0;
        }
        if (!lim && f[pos] !== -1) {
            return f[pos];
        }
        if (t.length - pos === s.length) {
            if (lim) {
                return s <= t.substring(pos) ? 1 : 0;
            }
            return 1;
        }

        let ans: number = 0;
        const up: number = Math.min(lim ? +t[pos] : 9, limit);
        for (let i = 0; i <= up; i++) {
            ans += dfs(pos + 1, lim && i === +t[pos]);
        }

        if (!lim) {
            f[pos] = ans;
        }
        return ans;
    };

    const a: number = dfs(0, true);
    t = finish.toString();
    f = Array(20).fill(-1);
    const b: number = dfs(0, true);

    return b - a;
}

###cs

public class Solution {
    private string s;
    private string t;
    private long?[] f;
    private int limit;

    public long NumberOfPowerfulInt(long start, long finish, int limit, string s) {
        this.s = s;
        this.limit = limit;
        t = (start - 1).ToString();
        f = new long?[20];
        long a = Dfs(0, true);
        t = finish.ToString();
        f = new long?[20];
        long b = Dfs(0, true);
        return b - a;
    }

    private long Dfs(int pos, bool lim) {
        if (t.Length < s.Length) {
            return 0;
        }
        if (!lim && f[pos].HasValue) {
            return f[pos].Value;
        }
        if (t.Length - pos == s.Length) {
            return lim ? (string.Compare(s, t.Substring(pos)) <= 0 ? 1 : 0) : 1;
        }
        int up = lim ? t[pos] - '0' : 9;
        up = Math.Min(up, limit);
        long ans = 0;
        for (int i = 0; i <= up; ++i) {
            ans += Dfs(pos + 1, lim && i == (t[pos] - '0'));
        }
        if (!lim) {
            f[pos] = ans;
        }
        return ans;
    }
}

时间复杂度 $O(\log M \times D)$,空间复杂度 $O(\log M)$,其中 $M$ 为数字的上限,而 $D = 10$。


有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~

每日一题-统计强大整数的数目🔴

2025年4月10日 00:00

给你三个整数 start ,finish 和 limit 。同时给你一个下标从 0 开始的字符串 s ,表示一个  整数。

如果一个  整数 x 末尾部分是 s (换句话说,s 是 x 的 后缀),且 x 中的每个数位至多是 limit ,那么我们称 x 是 强大的 。

请你返回区间 [start..finish] 内强大整数的 总数目 。

如果一个字符串 x 是 y 中某个下标开始(包括 0 ),到下标为 y.length - 1 结束的子字符串,那么我们称 x 是 y 的一个后缀。比方说,25 是 5125 的一个后缀,但不是 512 的后缀。

 

示例 1:

输入:start = 1, finish = 6000, limit = 4, s = "124"
输出:5
解释:区间 [1..6000] 内的强大数字为 124 ,1124 ,2124 ,3124 和 4124 。这些整数的各个数位都 <= 4 且 "124" 是它们的后缀。注意 5124 不是强大整数,因为第一个数位 5 大于 4 。
这个区间内总共只有这 5 个强大整数。

示例 2:

输入:start = 15, finish = 215, limit = 6, s = "10"
输出:2
解释:区间 [15..215] 内的强大整数为 110 和 210 。这些整数的各个数位都 <= 6 且 "10" 是它们的后缀。
这个区间总共只有这 2 个强大整数。

示例 3:

输入:start = 1000, finish = 2000, limit = 4, s = "3000"
输出:0
解释:区间 [1000..2000] 内的整数都小于 3000 ,所以 "3000" 不可能是这个区间内任何整数的后缀。

 

提示:

  • 1 <= start <= finish <= 1015
  • 1 <= limit <= 9
  • 1 <= s.length <= floor(log10(finish)) + 1
  • s 数位中每个数字都小于等于 limit 。
  • s 不包含任何前导 0 。

进制转换

2024年1月12日 20:59

Problem: 2999. 统计强大整数的数目

[toc]

思路

可以将题目理解为:把 $s$ 看成一个数的后缀,在它的前面加上一个前缀,使得新的数在 $[start,finish]$ 之中,并且数的每一位数都要小于等于 $limit$。显然,加的这个前缀应当有个范围:$[low,upper]$。
每一位数都要小于等于 $limit$,可以想到这是一个 $limit+1$进制数(后面就叫它 $li$ 进制数)。
那所要求的答案便是,将 $low$ 和 $upper$ 看成是 $li$进制数之后,二者的差加 $1$(因为是闭区间)。

那接下来就是怎么求 $low$ 和 $upper$ 了。

求下界 $low$

先求 $low$ 。
注意:虽然都叫 $low$,但现在开始讨论的 $low$ 是带后缀的,结束之后去掉后缀得到前面说的那个 “前缀$low$”。后面 $upper$ 也是。
注意2:题目给的 $s$ 是字符串,但有的时候我就直接当整型啦~

下界 $low$ 要符合一些要求:

  1. 要在 $[start,finish]$ 内,也就是 $\ge start$
  2. 后缀要是 $s$,所以 $\ge s$
  3. 每一位上的数都要 $\le limit$

由 $1$ 和 $2$ 可以将 $low$ 初始值设置为 $max(start,s)$。

再看每一位的值。
假设现在的 $low$ 为 $\underline{a} \space \underline{b} \space \underline{c} \space \underline{{\color{Red} r} } \space \underline{x} \space \underline{y} \space \underline{z} $,某一位上的值是 $r$ 且 $r \gt limit$,那么比这 $low$ 大的(要找下界肯定不能往小的方向去考虑吧,往小的方向考虑说不定就比 $start$ 还小了呢。同样地,上界就应当往小的方向考虑)又要保证每一位数都 $\le limit$ 的最小的数是多少呢?那应该是 $ \underline{a} \space \underline{b} \space \underline{c+1} \space \underline{{\color{Red} 0} } \space \underline{0} \space \underline{0} \space \underline{0} $ 吧,也就是向前进位一位,同时将低位的值都变为 $0$。

因为要往前进位一位,而进位之后可能也会超过 $limit$,所以要从低位开始遍历。

还有一件事情,要提前做。
现在这个 $low$ 的后缀,如果比 $s$ 要大,那它的前缀部分也应当要加 $1$ 后缀部分全部变为 $0$,也就是和前面那样的操作。
假设现在 $low$ 为 $ \underline{low \underline{\space}pre} \space \underline{low\underline{\space}suf} $,其中 $ low\underline{\space}suf \gt s$,那么,为了找到后缀是 $s$ 的数,又不能往小了考虑,就要变大 $low$,让 $low$ 变成 $ \underline{low \underline{\space}pre+1} \space \underline{s} $。为了方便以及和前统一,也就将后缀每一位变为 $0$ ,这不影响。

现在得到了带后缀的下界 $low$,将后缀“去掉”,就得到了前面所说的“前缀范围”的那个 $low$了。

求上界 $upper$

再说 $upper$ .大思路和求 $low$ 差不多。

上界 $upper$ 要符合一些要求:

  1. 要在 $[start,finish]$ 内,也就是 $upper \le finish$
  2. 每一位上的数都要 $\le limit$

由 $1$ 可以将 $upper$ 初始值设置为 $finish$。同时,可以得到两个特判:当 $s \gt finish$ 的时候,可以直接 return 0 了,因为不会有在 $[start,finish]$ 的数;当 $s == finish$ 的时候,可以直接 return 1

再看每一位的值。
假设现在的 $upper$ 为 $\underline{a} \space \underline{b} \space \underline{c} \space \underline{{\color{Red} r} } \space \underline{x} \space \underline{y} \space \underline{z} $,某一位上的值是 $r$ 且 $r \gt limit$,那么比这 $upper$ 小的(为什么是小前面有说哦)又要保证每一位数都 $\lt limit$ 的最大的数是多少呢?那应该是 $ \underline{a} \space \underline{b} \space \underline{c} \space \underline{{\color{Red} limit} } \space \underline{limit} \space \underline{limit} \space \underline{limit} $ 吧,也就是从当前位开始往低位全部变为 $limit$。

因为要修改低位的值,所以选择从高位开始遍历。

提前要做的事情。
假设现在的 $upper$ 是 $ \underline{upper\underline{\space}pre} \space \underline{upper\underline{\space}suf} $,其中 $ upper\underline{\space}suf \lt s$,这样的 $upper$ 是大了的,要变成 $ \underline{upper\underline{\space}pre-1} \space \underline{upper\underline{\space}suf} $ 才行。

现在得到了带后缀的上界 $upper$,将后缀“去掉”,就得到了开始所说的“前缀范围”的那个 $upper$ 了。

Code

###Python3

class Solution:
    def numberOfPowerfulInt(self, start: int, finish: int, limit: int, s: str) -> int:
        s_int = int(s)
        if s_int >= finish:  # s 本身就比 finish 大,那在 [start,finish] 里就没有强数了,但如果 s==finish,还能有一个
            return int(s_int == finish)

        def int_len(x: int) -> int:
            # 计算 整数 x 的长度
            ans = 0
            while x:
                ans += 1
                x //= 10
            return ans

        s_len = len(s)
        sk = 10 ** s_len  # 经常用到

        low = max(s_int, start)
        if s_int < start % sk:
            # 向前进位,后面全减少为0
            low += sk - start % sk
        # 每一位都要比 limit 小或等于,哪一位大了往前进位,再将这一位及其往后的变为 0
        for k in range(s_len, int_len(low)):
            kk = pow(10, k)
            r = low // kk % 10  # 当前这一位的值
            if r > limit:
                low += (10 - r) * kk  # 当前位往前进 1 # low += kk * 10
                low = low // kk * kk  # 将后面全部变成 0
        low //= sk

        upper = finish - (10 ** s_len if s_int > finish % sk else 0)
        # 每一位都要比 limit 小或等于,哪一位大了就把这一位及其往后都变成 limit
        flag = False
        for k in range(int_len(upper), s_len - 1, -1):
            kk = pow(10, k)
            r = upper // kk % 10  # 当前这一位的值
            if flag or r > limit:  # 当前这一位变为 limit
                upper -= (r - limit) * kk
                flag = True
        upper //= sk

        def f(x: int) -> int:
            li = limit + 1
            # 将 li进制 数转为十进制数
            ans = 0
            k = 1
            while x:
                ans += x % 10 * k
                k *= li
                x //= 10
            return ans

        return f(upper) - f(low) + 1

复杂度

时间复杂度:$O( \log_{10}{start} + \log_{10}{finish})$ ,时间复杂度与 $start$ 和 $finish$ 的数位长度有关。
空间复杂度:$O(1)$,只有一些额外变量。

面向解答错误做题😪

7次解答错误的提交记录

【模板】上下界数位 DP(Python/Java/C++/Go)

作者 endlesscheng
2024年1月7日 21:02

v1.0 视频讲解

v2.0 视频讲解

定义 $\textit{dfs}(i,\textit{limitLow},\textit{limitHigh})$ 表示构造第 $i$ 位及其之后数位的合法方案数,其余参数的含义为:

  • $\textit{limitHigh}$ 表示当前是否受到了 $\textit{finish}$ 的约束(我们要构造的数字不能超过 $\textit{finish}$)。若为真,则第 $i$ 位填入的数字至多为 $\textit{finish}[i]$,否则至多为 $9$,这个数记作 $\textit{hi}$。如果在受到约束的情况下填了 $\textit{finish}[i]$,那么后续填入的数字仍会受到 $\textit{finish}$ 的约束。例如 $\textit{finish}=123$,那么 $i=0$ 填的是 $1$ 的话,$i=1$ 的这一位至多填 $2$。
  • $\textit{limitLow}$ 表示当前是否受到了 $\textit{start}$ 的约束(我们要构造的数字不能低于 $\textit{start}$)。若为真,则第 $i$ 位填入的数字至少为 $\textit{start}[i]$,否则至少为 $0$,这个数记作 $\textit{lo}$。如果在受到约束的情况下填了 $\textit{start}[i]$,那么后续填入的数字仍会受到 $\textit{start}$ 的约束。

枚举第 $i$ 位填什么数字。

如果 $i< n - |s|$,那么可以填 $[\textit{lo}, \min(\textit{hi}, \textit{limit})]$ 内的数,否则只能填 $s[i-(n-|s|)]$。这里 $|s|$ 表示 $s$ 的长度。

为什么不能把 $\textit{hi}$ 置为 $\min(\textit{hi}, \textit{limit})$?请看 视频 中举的反例。

递归终点:$\textit{dfs}(n,,)=1$,表示成功构造出一个合法数字。

递归入口:$\textit{dfs}(0, \texttt{true}, \texttt{true})$,表示:

  • 从最高位开始枚举。
  • 一开始要受到 $\textit{start}$ 和 $\textit{finish}$ 的约束(否则就可以随意填了,这肯定不行)。

答疑

:记忆化三个状态有点麻烦,能不能只记忆化 $i$ 这个状态?

:是可以的。比如 $\textit{finish}=234$,第一位填 $2$,第二位填 $3$,后面无论怎么递归,都不会再次递归到第一位填 $2$,第二位填 $3$ 的情况,所以不需要记录。(注:想象我们在写一个三重循环,枚举每一位填什么数字。第一位填 $2$,第二位填 $3$ 已经是快要结束循环的情况了,不可能再次枚举到。)对于 $\textit{start}$ 也同理。

根据这个例子,我们可以只记录不受到 $\textit{limitLow}$ 或 $\textit{limitHigh}$ 约束时的状态 $i$。相当于记忆化的是 $(i,\texttt{false},\texttt{false})$ 这个状态,因为其它状态只会递归访问一次。

###py

class Solution:
    def numberOfPowerfulInt(self, start: int, finish: int, limit: int, s: str) -> int:
        high = list(map(int, str(finish)))  # 避免在 dfs 中频繁调用 int()
        n = len(high)
        low = list(map(int, str(start).zfill(n)))  # 补前导零,和 high 对齐
        diff = n - len(s)

        @cache
        def dfs(i: int, limit_low: bool, limit_high: bool) -> int:
            if i == n:
                return 1

            # 第 i 个数位可以从 lo 枚举到 hi
            # 如果对数位还有其它约束,应当只在下面的 for 循环做限制,不应修改 lo 或 hi
            lo = low[i] if limit_low else 0
            hi = high[i] if limit_high else 9

            res = 0
            if i < diff:  # 枚举这个数位填什么
                for d in range(lo, min(hi, limit) + 1):
                    res += dfs(i + 1, limit_low and d == lo, limit_high and d == hi)
            else:  # 这个数位只能填 s[i-diff]
                x = int(s[i - diff])
                if lo <= x <= hi:  # 题目保证 x <= limit,无需判断
                    res = dfs(i + 1, limit_low and x == lo, limit_high and x == hi)
            return res

        return dfs(0, True, True)

###java

class Solution {
    public long numberOfPowerfulInt(long start, long finish, int limit, String s) {
        String low = Long.toString(start);
        String high = Long.toString(finish);
        int n = high.length();
        low = "0".repeat(n - low.length()) + low; // 补前导零,和 high 对齐
        long[] memo = new long[n];
        Arrays.fill(memo, -1);
        return dfs(0, true, true, low.toCharArray(), high.toCharArray(), limit, s.toCharArray(), memo);
    }

    private long dfs(int i, boolean limitLow, boolean limitHigh, char[] low, char[] high, int limit, char[] s, long[] memo) {
        if (i == high.length) {
            return 1;
        }

        if (!limitLow && !limitHigh && memo[i] != -1) {
            return memo[i]; // 之前计算过
        }

        // 第 i 个数位可以从 lo 枚举到 hi
        // 如果对数位还有其它约束,应当只在下面的 for 循环做限制,不应修改 lo 或 hi
        int lo = limitLow ? low[i] - '0' : 0;
        int hi = limitHigh ? high[i] - '0' : 9;

        long res = 0;
        if (i < high.length - s.length) { // 枚举这个数位填什么
            for (int d = lo; d <= Math.min(hi, limit); d++) {
                res += dfs(i + 1, limitLow && d == lo, limitHigh && d == hi, low, high, limit, s, memo);
            }
        } else { // 这个数位只能填 s[i-diff]
            int x = s[i - (high.length - s.length)] - '0';
            if (lo <= x && x <= hi) { // 题目保证 x <= limit,无需判断
                res = dfs(i + 1, limitLow && x == lo, limitHigh && x == hi, low, high, limit, s, memo);
            }
        }

        if (!limitLow && !limitHigh) {
            memo[i] = res; // 记忆化 (i,false,false)
        }
        return res;
    }
}

###cpp

class Solution {
public:
    long long numberOfPowerfulInt(long long start, long long finish, int limit, string s) {
        string low = to_string(start);
        string high = to_string(finish);
        int n = high.size();
        low = string(n - low.size(), '0') + low; // 补前导零,和 high 对齐
        int diff = n - s.size();

        vector<long long> memo(n, -1);
        auto dfs = [&](this auto&& dfs, int i, bool limit_low, bool limit_high) -> long long {
            if (i == low.size()) {
                return 1;
            }

            if (!limit_low && !limit_high && memo[i] != -1) {
                return memo[i]; // 之前计算过
            }

            // 第 i 个数位可以从 lo 枚举到 hi
            // 如果对数位还有其它约束,应当只在下面的 for 循环做限制,不应修改 lo 或 hi
            int lo = limit_low ? low[i] - '0' : 0;
            int hi = limit_high ? high[i] - '0' : 9;

            long long res = 0;
            if (i < diff) { // 枚举这个数位填什么
                for (int d = lo; d <= min(hi, limit); d++) {
                    res += dfs(i + 1, limit_low && d == lo, limit_high && d == hi);
                }
            } else { // 这个数位只能填 s[i-diff]
                int x = s[i - diff] - '0';
                if (lo <= x && x <= hi) { // 题目保证 x <= limit,无需判断
                    res = dfs(i + 1, limit_low && x == lo, limit_high && x == hi);
                }
            }

            if (!limit_low && !limit_high) {
                memo[i] = res; // 记忆化 (i,false,false)
            }
            return res;
        };
        return dfs(0, true, true);
    }
};

###go

func numberOfPowerfulInt(start, finish int64, limit int, s string) int64 {
low := strconv.FormatInt(start, 10)
high := strconv.FormatInt(finish, 10)
n := len(high)
low = strings.Repeat("0", n-len(low)) + low // 补前导零,和 high 对齐
diff := n - len(s)

memo := make([]int64, n)
for i := range memo {
memo[i] = -1
}
var dfs func(int, bool, bool) int64
dfs = func(i int, limitLow, limitHigh bool) (res int64) {
if i == n {
return 1
}

if !limitLow && !limitHigh {
p := &memo[i]
if *p >= 0 {
return *p
}
defer func() { *p = res }()
}

// 第 i 个数位可以从 lo 枚举到 hi
// 如果对数位还有其它约束,应当只在下面的 for 循环做限制,不应修改 lo 或 hi
lo := 0
if limitLow {
lo = int(low[i] - '0')
}
hi := 9
if limitHigh {
hi = int(high[i] - '0')
}

if i < diff { // 枚举这个数位填什么
for d := lo; d <= min(hi, limit); d++ {
res += dfs(i+1, limitLow && d == lo, limitHigh && d == hi)
}
} else { // 这个数位只能填 s[i-diff]
x := int(s[i-diff] - '0')
if lo <= x && x <= hi { // 题目保证 x <= limit,无需判断
res += dfs(i+1, limitLow && x == lo, limitHigh && x == hi)
}
}
return
}
return dfs(0, true, true)
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(nD)$,其中 $n=\mathcal{O}(\log \textit{finish})$,$D=10$。由于每个状态只会计算一次,动态规划的时间复杂度 $=$ 状态个数 $\times$ 单个状态的计算时间。本题状态个数等于 $\mathcal{O}(n)$,单个状态的计算时间为 $\mathcal{O}(D)$,所以动态规划的时间复杂度为 $\mathcal{O}(nD)$。
  • 空间复杂度:$\mathcal{O}(n)$。即状态个数。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
  7. 【本题相关】动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

不用数位DP的计数方法

作者 FreeYourMind
2024年1月7日 00:59

###Python3

class Solution:
    def numberOfPowerfulInt(self, start: int, finish: int, limit: int, s: str) -> int:
        x, m = int(s), len(s)

        def ceil(y: int):
            sy = str(y)
            pre, suf = int("0" + sy[:-m]), int(sy[-m:])
            return pre + int(suf >= x)

        def count(y: int) -> int:
            n, res = len(str(y)), 0
            for i, d in enumerate(map(int, str(y))):
                if limit < d:
                    return res + pow(limit + 1, n - i)
                res += d * pow(limit + 1, n - i - 1)
            return res

        b, a = ceil(finish), ceil(start - 1)
        return count(b) - count(a)
昨天以前LeetCode 每日一题题解

使数组的值全部为 K 的最少操作次数

2025年4月1日 11:03

方法一:哈希表

思路与算法

根据题意,若当前数组的最大值为 $x$,次大值(若有的话)为 $y$,那么我们可以选择一个 $h(y \le h \lt x)$,令数组中所有等于 $x$ 的数字都变成 $h$。

因此,若要使用最小的操作次数把整个数组中的数字都变成 $k$,那么:

  • 若数组存在比 $k$ 小的数字,则无解;
  • 否则,统计数组中所有大于 $k$ 的数字种类个数,该个数即为操作次数;

我们用一个哈希表去统计数组中大于 $k$ 的数字。在遍历数组的过程中,若遇到比 $k$ 小的则直接返回 $-1$。

代码

###C++

class Solution {
public:
    int minOperations(vector<int>& nums, int k) {
        unordered_set<int> st;
        for (int x : nums) {
            if (x < k) {
                return -1;
            } else if (x > k) {
                st.insert(x);
            }
        }
        return st.size();
    }
};

###Python

class Solution:
    def minOperations(self, nums: List[int], k: int) -> int:
        st = set()
        for x in nums:
            if x < k:
                return -1
            elif x > k:
                st.add(x)
        return len(st)

###Rust

use std::collections::HashSet;
impl Solution {
    pub fn min_operations(nums: Vec<i32>, k: i32) -> i32 {
        let mut st = HashSet::new();
        for x in nums {
            if x < k {
                return -1
            } else if x > k {
                st.insert(x);
            }
        }
        st.len() as i32
    }
}

###Java

class Solution {
    public int minOperations(int[] nums, int k) {
        Set<Integer> st = new HashSet<>();
        for (int x : nums) {
            if (x < k) {
                return -1;
            } else if (x > k) {
                st.add(x);
            }
        }
        return st.size();
    }
}

###C#

public class Solution {
    public int MinOperations(int[] nums, int k) {
        HashSet<int> st = new HashSet<int>();
        foreach (int x in nums) {
            if (x < k) {
                return -1;
            } else if (x > k) {
                st.Add(x);
            }
        }
        return st.Count;
    }
}

###Go

func minOperations(nums []int, k int) int {
    st := make(map[int]bool)
    for _, x := range nums {
        if x < k {
            return -1
        } else if x > k {
            st[x] = true
        }
    }
    return len(st)
}

###C

typedef struct {
    int key;
    UT_hash_handle hh;
} HashItem;

HashItem *hashFindItem(HashItem **obj, int key) {
    HashItem *pEntry = NULL;
    HASH_FIND_INT(*obj, &key, pEntry);
    return pEntry;
}

bool hashAddItem(HashItem **obj, int key) {
    if (hashFindItem(obj, key)) {
        return false;
    }
    HashItem *pEntry = (HashItem *)malloc(sizeof(HashItem));
    pEntry->key = key;
    HASH_ADD_INT(*obj, key, pEntry);
    return true;
}

void hashFree(HashItem **obj) {
    HashItem *curr = NULL, *tmp = NULL;
    HASH_ITER(hh, *obj, curr, tmp) {
        HASH_DEL(*obj, curr);  
        free(curr);
    }
}
int minOperations(int* nums, int numsSize, int k) {
    HashItem *st = NULL;
    for (int i = 0; i < numsSize; i++) {
        if (nums[i] < k) {
            return -1;
        } else if (nums[i] > k) {
            hashAddItem(&st, nums[i]);
        }
    }
    int ret = HASH_COUNT(st);
    hashFree(&st);
    return ret;
}

###JavaScript

var minOperations = function(nums, k) {
    const st = new Set();
    for (const x of nums) {
        if (x < k) {
            return -1;
        } else if (x > k) {
            st.add(x);
        }
    }
    return st.size;
};

###TypeScript

function minOperations(nums: number[], k: number): number {
    const st = new Set<number>();
    for (const x of nums) {
        if (x < k) {
            return -1;
        } else if (x > k) {
            st.add(x);
        }
    }
    return st.size;
};

复杂度分析

  • 时间复杂度:$O(n)$,其中 $n$ 是 $\textit{nums}$ 的长度。我们仅需遍历一次 $\textit{nums}$,并且向哈希表中添加元素的时间复杂度为 $O(1)$,因此总体时间复杂度为 $O(n)$。

  • 空间复杂度:$O(n)$。使用哈希表的空间复杂度为 $O(n)$。

[Python3/Java/C++/Go/TypeScript] 一题一解:哈希表(清晰题解)

作者 lcbin
2025年4月9日 06:36

方法一:哈希表

根据题目描述,我们每次可以选择当前数组中的次大值作为合法整数 $h$,将所有大于 $h$ 的数都变为 $h$,这样可以使得操作次数最少。另外,由于操作会使得数字变小,因此,如果当前数组中存在小于 $k$ 的数,那么我们就无法将所有数都变为 $k$,直接返回 -1 即可。

我们遍历数组 $\textit{nums}$,对于当前的数 $x$,如果 $x < k$,直接返回 -1;否则,我们将 $x$ 加入哈希表中,并且更新当前数组中的最小值 $\textit{mi}$。最后,我们返回哈希表的大小减去 1(如果 $\textit{mi} = k$,则需要减去 1)。

###python

class Solution:
    def minOperations(self, nums: List[int], k: int) -> int:
        s = set()
        mi = inf
        for x in nums:
            if x < k:
                return -1
            mi = min(mi, x)
            s.add(x)
        return len(s) - int(k == mi)

###java

class Solution {
    public int minOperations(int[] nums, int k) {
        Set<Integer> s = new HashSet<>();
        int mi = 1 << 30;
        for (int x : nums) {
            if (x < k) {
                return -1;
            }
            mi = Math.min(mi, x);
            s.add(x);
        }
        return s.size() - (mi == k ? 1 : 0);
    }
}

###cpp

class Solution {
public:
    int minOperations(vector<int>& nums, int k) {
        unordered_set<int> s;
        int mi = INT_MAX;
        for (int x : nums) {
            if (x < k) {
                return -1;
            }
            mi = min(mi, x);
            s.insert(x);
        }
        return s.size() - (mi == k);
    }
};

###go

func minOperations(nums []int, k int) int {
mi := 1 << 30
s := map[int]bool{}
for _, x := range nums {
if x < k {
return -1
}
s[x] = true
mi = min(mi, x)
}
if mi == k {
return len(s) - 1
}
return len(s)
}

###ts

function minOperations(nums: number[], k: number): number {
    const s = new Set<number>();
    let mi = Infinity;
    for (const x of nums) {
        if (x < k) {
            return -1;
        }
        s.add(x);
        mi = Math.min(mi, x);
    }
    return s.size - (mi === k ? 1 : 0);
}

###rust

impl Solution {
    pub fn min_operations(nums: Vec<i32>, k: i32) -> i32 {
        use std::collections::HashSet;

        let mut s = HashSet::new();
        let mut mi = i32::MAX;

        for &x in &nums {
            if x < k {
                return -1;
            }
            s.insert(x);
            mi = mi.min(x);
        }

        (s.len() as i32) - if mi == k { 1 } else { 0 }
    }
}

时间复杂度 $O(n)$,空间复杂度 $O(n)$。其中 $n$ 是数组 $\textit{nums}$ 的长度。


有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~

每日一题-使数组的值全部为 K 的最少操作次数🟢

2025年4月9日 00:00

给你一个整数数组 nums 和一个整数 k 。

如果一个数组中所有 严格大于 h 的整数值都 相等 ,那么我们称整数 h 是 合法的 。

比方说,如果 nums = [10, 8, 10, 8] ,那么 h = 9 是一个 合法 整数,因为所有满足 nums[i] > 9 的数都等于 10 ,但是 5 不是 合法 整数。

你可以对 nums 执行以下操作:

  • 选择一个整数 h ,它对于 当前 nums 中的值是合法的。
  • 对于每个下标 i ,如果它满足 nums[i] > h ,那么将 nums[i] 变为 h 。

你的目标是将 nums 中的所有元素都变为 k ,请你返回 最少 操作次数。如果无法将所有元素都变 k ,那么返回 -1 。

 

示例 1:

输入:nums = [5,2,5,4,5], k = 2

输出:2

解释:

依次选择合法整数 4 和 2 ,将数组全部变为 2 。

示例 2:

输入:nums = [2,1,2], k = 2

输出:-1

解释:

没法将所有值变为 2 。

示例 3:

输入:nums = [9,7,5,3], k = 1

输出:4

解释:

依次选择合法整数 7 ,5 ,3 和 1 ,将数组全部变为 1 。

 

提示:

  • 1 <= nums.length <= 100
  • 1 <= nums[i] <= 100
  • 1 <= k <= 100

本质是计算不同元素个数(Python/Java/C++/Go/JS/Rust)

作者 endlesscheng
2024年12月8日 09:11

思考框架

  1. 理解操作在做什么。
  2. 把 $\textit{nums}$ 中的数都变成一样的,能变成哪些数?
  3. 如何最小化操作次数?

操作在做什么

题目说「选择一个整数 $h$」,哪些 $h$ 是可以选的?

根据「合法」的定义,$h$ 不能低于 $\textit{nums}$ 的次大值。比如 $\textit{nums}=[5,2,5,4,5]$,$h$ 不能小于次大值 $4$,否则大于 $h$ 的数不都相等。

所以操作只能改变大于次大值的数,也就是最大值

能变成哪些数

要让所有数都变成 $k$,前提条件是所有数都变成一样的。那么,能变成哪些数呢?

仍然以 $\textit{nums}=[5,2,5,4,5]$ 为例。选择 $h=4$,可以把最大值 $5$ 改成次大值 $4$。修改后 $\textit{nums}=[4,2,4,4,4]$,有更多的数都相同。并且修改后,原来的次大值 $4$ 就变成最大值了。下一次操作,我们就可以选择更小的 $h$,把更多的数都变成一样的。

选择 $h=2$,可以把最大值 $4$ 改成次大值 $2$。修改后 $\textit{nums}=[2,2,2,2,2]$,所有数都相同。

如果想继续改成比 $2$ 小的数,比如 $0$,选择 $h=0$ 即可。

所以,$\textit{nums}$ 中的数可以都变成任意 $\le \min(\textit{nums})$ 的数。

最小化操作次数

为了最小化操作次数,每次选 $h$ 为次大值是最优的。贪心地想,能一步到达次大值,没必要分好几步。

分类讨论:

  • 如果 $k > \min(nums)$,无法满足要求,返回 $-1$。
  • 如果 $k = \min(nums)$,操作次数为 $\textit{nums}$ 中的不同元素个数减一。比如 $[5,2,5,4,5]\to [4,2,4,4,4]\to [2,2,2,2,2]$,最大值 $5\to 4\to 2$,用了 $2$ 次操作。
  • 如果 $k < \min(nums)$,操作次数为 $\textit{nums}$ 中的不同元素个数。因为都变成 $\min(nums)$ 后,还需要再操作一次,才能都变成 $k$。

###py

class Solution:
    def minOperations(self, nums: List[int], k: int) -> int:
        mn = min(nums)
        if k > mn:
            return -1
        return len(set(nums)) - (k == mn)

###java

class Solution {
    public int minOperations(int[] nums, int k) {
        int min = Arrays.stream(nums).min().getAsInt();
        if (k > min) {
            return -1;
        }
        int distinctCount = (int) Arrays.stream(nums).distinct().count();
        return distinctCount - (k == min ? 1 : 0);
    }
}

###cpp

class Solution {
public:
    int minOperations(vector<int>& nums, int k) {
        int mn = ranges::min(nums);
        if (k > mn) {
            return -1;
        }
        unordered_set<int> st(nums.begin(), nums.end());
        return st.size() - (k == mn);
    }
};

###go

func minOperations(nums []int, k int) int {
    mn := slices.Min(nums)
    if k > mn {
        return -1
    }

    set := map[int]struct{}{}
    for _, x := range nums {
        set[x] = struct{}{}
    }
    if k == mn {
        return len(set) - 1
    }
    return len(set)
}

###js

var minOperations = function(nums, k) {
    const min = Math.min(...nums);
    if (k > min) {
        return -1;
    }
    return new Set(nums).size - (k === min ? 1 : 0);
};

###rust

use std::collections::HashSet;

impl Solution {
    pub fn min_operations(nums: Vec<i32>, k: i32) -> i32 {
        let min = *nums.iter().min().unwrap();
        if k > min {
            return -1;
        }
        let set = nums.into_iter().collect::<HashSet<_>>();
        set.len() as i32 - if k == min { 1 } else { 0 }
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是 $\textit{nums}$ 的长度。
  • 空间复杂度:$\mathcal{O}(n)$。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
  7. 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

❌
❌