首页 > 其他分享 >力扣题解2376

力扣题解2376

时间:2024-09-20 23:24:15浏览次数:3  
标签:obj 数字 int 题解 整数 力扣 key 2376 HashItem

大家好,欢迎来到无限大的频道。

今日继续给大家带来力扣题解。

题目描述(困难):

统计特殊整数

如果一个正整数每一个数位都是 互不相同 的,我们称它是 特殊整数 。

给你一个 正 整数 n ,请你返回区间 [1, n] 之间特殊整数的数目。

​解题思路:

要计算区间 ([1, n]) 之间的特殊整数数量,可以按以下步骤进行:

  1. 定义特殊整数:

    • 一个特殊整数是指其每一个数位都是互不相同的,即没有重复的数字。

  2. 遍历和检查:

    • 遍历每一个整数从 1 到 n,检查每个整数是否为特殊整数。

    • 对于每个整数,可以将其分解为其各个数字,并检查这些数字是否有重复。

  3. 利用集合判断重复:

    • 可以通过一个数组或集合来记录某个数字是否已经出现。

  4. 计数特殊整数:

    • 如果当前数字是特殊整数,将计数器加一。遍历结束后,输出计数器的值。

参考代码如下:

bool is_special_integer(int num) {
    bool digit_seen[10] = {false}; // 标记每个数字是否出现过
    while (num > 0) {
        int digit = num % 10; // 取出当前的末尾数字
        if (digit_seen[digit]) { // 如果该数字已经出现过
            return false; // 不是特殊整数
        }
        digit_seen[digit] = true; // 标记该数字出现过
        num /= 10; // 去掉当前数字
    }
    return true; // 所有数字都互不相同
}
​
int countSpecialNumbers(int n) {
    int count = 0; // 特殊整数计数器
    for (int i = 1; i <= n; i++) {
        if (is_special_integer(i)) { // 判断是否为特殊整数
            count++; // 增加计数
        }
    }
    return count; // 返回特殊整数的数量
}

代码解析:

  1. is_special_integer 函数:

    • 输入一个整数 num 并检查其每位数字是否互不相同。使用一个布尔数组 digit_seen 来记录 0-9 这 10 个数字的出现情况。

  2. countSpecialNumbers 函数:

    • 遍历 1 到 n 的所有整数,调用 is_special_integer 函数来判断该数字是否是特殊的,并更新计数器。

但是此代码的时间复杂度过高,测试用例超过了限定时间,所以要进行优化。

解题思路:

  1. 理解特殊整数:

    • 特殊整数是在其每一位上没有重复的数字(例如,123、456是特殊整数,但 112、121、123 等不是)。

  2. 动态规划 + 记忆化搜索:

    • mask:用于表示当前已经使用的数字,采用位掩码。

    • prefixSmaller:表示当前构建的前缀数字是否小于目标数字的前缀。

    • nStr:将 ( n ) 转换成字符串形式,以便逐位处理各数字。

    • 使用动态规划和哈希表来存储已经计算过的状态,以避免重复计算,提升效率。

    • 状态定义:

  3. 递归函数 (DP):

    • 通过递归函数 dp 实现动态规划,当当前使用的数字数量等于 ( n ) 的位数时返回 1。

    • 对于每个数字,可以根据当前的状态,在允许的范围内选择下一个数字(既 [0,9] 中未被使用的数字)。

    • 通过哈希表 memo 存储计算过的 mask 和 prefixSmaller 的组合,以加速后续计算。

  4. 计算统计:

    • 首先计算位数小于 ( n ) 的所有特殊整数数量;

    • 然后计算与 ( n ) 有相同位数的特殊整数数量。

参考代码如下:

typedef struct {
    int key;
    int val;
    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, int val) {
    if (hashFindItem(obj, key)) {
        return false;
    }
    HashItem *pEntry = (HashItem *)malloc(sizeof(HashItem));
    pEntry->key = key;
    pEntry->val = val;
    HASH_ADD_INT(*obj, key, pEntry);
    return true;
}
​
bool hashSetItem(HashItem **obj, int key, int val) {
    HashItem *pEntry = hashFindItem(obj, key);
    if (!pEntry) {
        hashAddItem(obj, key, val);
    } else {
        pEntry->val = val;
    }
    return true;
}
​
int hashGetItem(HashItem **obj, int key, int defaultVal) {
    HashItem *pEntry = hashFindItem(obj, key);
    if (!pEntry) {
        return defaultVal;
    }
    return pEntry->val;
}
​
void hashFree(HashItem **obj) {
    HashItem *curr = NULL, *tmp = NULL;
    HASH_ITER(hh, *obj, curr, tmp) {
        HASH_DEL(*obj, curr);  
        free(curr);
    }
}
​
int dp(int mask, bool prefixSmaller, const char *nStr, HashItem **memo) {
    if (__builtin_popcount(mask) == strlen(nStr)) {
        return 1;
    }
    int key = mask * 2 + (prefixSmaller ? 1 : 0);
    if (!hashFindItem(memo, key)) {
        int res = 0;
        int lowerBound = mask == 0 ? 1 : 0;
        int upperBound = prefixSmaller ? 9 : nStr[__builtin_popcount(mask)] - '0';
        for (int i = lowerBound; i <= upperBound; i++) {
            if (((mask >> i) & 1) == 0) {
                res += dp(mask | (1 << i), prefixSmaller || i < upperBound, nStr, memo);
            }
        }
        hashAddItem(memo, key, res);
    }
    return hashGetItem(memo, key, 0);
}
​
int countSpecialNumbers(int n) {
    char nStr[64];
    sprintf(nStr, "%d", n);
    int res = 0;
    int prod = 9;
    int len = strlen(nStr);
    HashItem *memo = NULL;
    for (int i = 0; i < len - 1; i++) {
        res += prod;
        prod *= 9 - i;
    }
    res += dp(0, false, nStr, &memo);
    hashFree(&memo);
    return res;
}

代码分析:

1. 哈希表结构与操作
typedef struct {
    int key;
    int val;
    UT_hash_handle hh; // 哈希表的内部处理
} HashItem; 
  • 定义了一个结构体 HashItem,用于存储键值对(key 和 val),并使用 UT_hash_handle 宏,以便利用 uthash 提供的哈希表功能。

  • 哈希表相关操作如下:

HashItem *hashFindItem(HashItem **obj, int key);
bool hashAddItem(HashItem **obj, int key, int val);
bool hashSetItem(HashItem **obj, int key, int val);
int hashGetItem(HashItem **obj, int key, int defaultVal);
void hashFree(HashItem **obj);
  • hashFindItem 查找哈希表的项。

  • hashAddItem 添加项到哈希表,如果已存在则返回 false。

  • hashSetItem 设置某项的值,如果不存在,则插入新项。

  • hashGetItem 获取项的值,若不存在则返回默认值。

  • hashFree 释放哈希表的内存。

2. 动态规划函数 dp
int dp(int mask, bool prefixSmaller, const char *nStr, HashItem **memo) {
    // 检查当前使用的数字数量是否等于目标字符串的数量
    if (__builtin_popcount(mask) == strlen(nStr)) {
        return 1; // 如果是,返回 1,表示成功构造了一个有效的特殊整数
    }
    
    // 生成一个唯一的键以访问缓存
    int key = mask * 2 + (prefixSmaller ? 1 : 0);
    
    // 如果当前状态没有被计算过
    if (!hashFindItem(memo, key)) {
        int res = 0;
        int lowerBound = (mask == 0) ? 1 : 0; // 第一个数字不能是 0
        // 确定当前可以选择的数字上限
        int upperBound = prefixSmaller ? 9 : nStr[__builtin_popcount(mask)] - '0';
        
        // 尝试选择每一个数字
        for (int i = lowerBound; i <= upperBound; i++) {
            if (((mask >> i) & 1) == 0) { // 检查当前数字是否未被使用
                // 递归调用,加入当前选择并更新状态
                res += dp(mask | (1 << i), prefixSmaller || (i < upperBound), nStr, memo);
            }
        }
        hashAddItem(memo, key, res); // 缓存当前状态的结果
    }
    return hashGetItem(memo, key, 0); // 返回缓存的结果或 0
}
  • dp 函数中,使用 mask 来表示当前使用的数字,利用位运算避免重复。

  • 使用 prefixSmaller 判断当前构造的数字是否小于对应的 ( n ) 的前缀。

  • 计算当前状态下所能构造的特殊整数数量,并将其缓存到哈希表中。

3. 主函数 countSpecialNumbers
int countSpecialNumbers(int n) {
    char nStr[64];
    sprintf(nStr, "%d", n); // 将 n 转换为字符串形式
    int res = 0;
    int prod = 9; // 可能的数字引导初始值为 9
    int len = strlen(nStr); // 目标长度
    HashItem *memo = NULL; // 初始化缓存
    
    // 计算所有位数小于 n 的特殊整数的数量
    for (int i = 0; i < len - 1; i++) {
        res += prod; // 将当前的数量加入结果
        prod *= 9 - i; // 更新可能的选择数
    }
    
    res += dp(0, false, nStr, &memo); // 计算与 nStr 长度相同的特殊整数
    hashFree(&memo); // 释放内存
    return res; // 返回总结果
}
  • 主函数中,首先将 ( n ) 转为字符串以便逐位处理。

  • 计算所有位数小于 ( n ) 的特殊整数数量。

  • 通过 dp 函数计算与 ( n ) 有相同位数的特殊整数数量。

  • 最后释放内存并返回结果。

总结:

整段代码结合了动态规划与哈希表的缓存机制,优雅地解决了问题:

  • 通过位掩码有效记录已使用的数字。

  • 通过记忆化搜索加速计算,避免重复递归。

  • 提供了高效的方式来计算区间 ([1, n]) 中所有特殊整数的数量,可以适用于较大的数字范围。

这种方法的时间复杂度主要由位数 ( m ) 影响,且结合了组合的计算,通常在实际问题中能够高效运行。

标签:obj,数字,int,题解,整数,力扣,key,2376,HashItem
From: https://blog.csdn.net/wxdzuishaui/article/details/142406705

相关文章

  • 题解 GD230531C【眺望】
    题目描述有\(n\)座灯塔排成一排,第\(i\)座灯塔的高度是\(a_i\)。你需要求出有多少对\(l<r\)满足\(a_l=a_r\),且\(\foralll<i<r,a_i<a_l\)。灯塔的高度有\(Q\)次修改,每次给定\(x,h\),表示将\(a_x\)修改为\(h\)。求出修改之前和每次修改之后的答案。\(n......
  • CF538G 题解
    CF538G题意\(s\)为长为\(l\)的由U、L、D、R组成的操作序列,一个机器人从\((0,0)\)开始按照\(s_{1\siml}\)的顺序循环行动\(+\infty\)次。给定n个形如\((t_i,x_i,y_i)\)的限制,表示第\(t_i\)时刻到达\((x_i,y_i)\)。构造\(s_{1\siml}\)。思路首先可以......
  • 力扣188-买卖股票的最佳时机 IV(Java详细题解)
    题目链接:188.买卖股票的最佳时机IV-力扣(LeetCode)前情提要:本题是由123.买卖股票的最佳时机III-力扣(LeetCode)变形而来,123是只能买卖俩次股票,该题是只能买卖k次股票,我相信当你做完这道题或者看完我上道题的题解,那么写这道题会轻松一点。因为本人最近都来刷dp类的题......
  • CF1977E 题解
    根据Dilworth定理,该图能被两条互不相交的链覆盖。从小到大加点。我们现在需要维护两个栈,每个栈维护每条链的点。若两个栈头没有连边,那么对于新加入的点,一定可以放到其中一个栈。现在唯一的问题是,新加入的点可能可以放入两个栈。我们可以再开一个栈三,用来维护以上述点为头的......
  • 1,Python数分之Pandas训练,力扣,1783. 大满贯数量
    学习:知识的初次邂逅复习:知识的温故知新练习:知识的实践应用目录 一,原题力扣链接二,题干三,建表语句四,分析四,Pandas解答:五,验证六,总结 一,原题力扣链接.-力扣(LeetCode)二,题干表:Players+----------------+---------+|ColumnName|Type|+--------......
  • 【Py/Java/C++三种语言OD独家2024E卷真题】20天拿下华为OD笔试之【模拟】2024E-转骰子
    可上欧弟OJ系统练习华子OD、大厂真题绿色聊天软件戳od1441了解算法冲刺训练(备注【CSDN】否则不通过)文章目录相关推荐阅读题目描述与示例题目描述输入描述输出描述示例一输入输出说明示例二输入输出说明解题思路构建长度为6的数组表......
  • 题解:洛谷P9934 [NFLSPC #6] 绝不能忘记的事……
    题目链接:洛谷P9934[NFLSPC#6]绝不能忘记的事……我hatelove大力分讨。这道题先分三种大情况:N在左边,N在中间,N在右边。声明:以下分类讨论中,a,b,c,d均为记住的字符串。记录操作N在左边当复制串形如Nab,可以用map<string,int>记录。当复制串形如NaH,那么......
  • 【问题解决】Web在线办公系统-数据爬取结果乱码
    问题描述在【热门电影】模块,通过jsoup爬虫并解析网页数据时,执行代码,出现“中文乱码”问题。解决方法由于网页自带的编码方式与后端开发中jsoup解析的编码方式不匹配,需要修改后端解析网页的编码方式。//设置爬取网页的地址Stringurl="https://movie.douban.com/......
  • Gradio离线部署到内网,资源加载失败问题(Gradio离线部署问题解决方法)
    问题描述Gradio作为一个快速构建一个演示或Web应用的开源Python包,被广泛使用,最近在用这个包进行AI应用构建,打包部署到内网Docker的时候发现有些资源无法使用。网页加载不出来。即使加载出来了也是没有样式无法点击的。一般出现这个问题的多半是低版本的gradio,高版本中已经解决......
  • 【力扣刷题】2090.半径为k的子数组平均值(定长滑动窗口)
    题目:给你一个下标从 0 开始的数组 nums ,数组中有 n 个整数,另给你一个整数 k 。半径为k的子数组平均值 是指:nums 中一个以下标 i 为 中心 且 半径 为 k 的子数组中所有元素的平均值,即下标在 i-k 和 i+k 范围(含 i-k 和 i+k)内所有元素的平均......