leetcode 编程技巧之二

1、参考 523. 连续的子数组和中,C语言中也可以HASH, 详细使用方法

uthash底层实现的数据结构就是一个双向链表

C语言哈希表uthash的使用方法详解(附下载链接) - 知乎

力扣

使用HASH_FIND_INT(g_users, &key, node) 查找node结点时,HASH_COUNT(node) 返回的是整个hash 中的元素总个数(并非当前node->key的子项的重复数),等价于HASH_COUNT(g_users)

2、背包问题总结:力扣

3、qsort 支持字符串指针的排序,eg: leetcode 524

char *dic[] = {"aaa", "ntgcykxhdfescxxypyew"}; // 字符串指针数组定义方法

qsort (dic, dictionarySize, sizeof (char *), comp);

// 长度长的优先,相等时字典序
int comp (const void* a, const void *b) {
    char *p1 = *(char**)a;   // 注意comp中的实现中需要二维指针
    char *p2 = *(char**)b;

    int s1 = strlen(p1);
    int s2 = strlen(p2);

    if (s1 != s2)
        return s2-s1;

    return strcmp (p1, p2);
}

3、统计最大值为10000的数字中各个数字的下标,leetcode 1202, 避免申请超大内存的方法,采用2次遍历

4、接口strtok的使用时第二次访问时第一个参数是NULL, 因此这个接口不能并行处理多个串, 参考C library function - strtok()

5、leetcode 629 介绍了记忆化搜索和动态规划的关系(两者不等价),详见力扣

6、myLowerBound和myUpperBound实现上的差异在于data[mid]==k时的差异,参考实现lower_bound和upper_bound算法_yang20141109的专栏-CSDN博客

leetcode 1231, 对于序列  9,8,8,7,6,6,5,5,4, 如果找最后一个6,那么上面二分法的框架第一个大于等于2 或者第一个大于2 均不符合要求,因此可以考虑将mid+1进行确认。(思考:需要找出一个mid值,按照mid值能分K+1份,而mid+1值不能分K+1份的最大mid值,显然mid+1值就是首个大于的UpperBound场景)

Leetcode 475 中二分法的右边界界是right = nums.length - 1

7、技巧:答案中每个坐标的visit元素是多个值,确保仅排除在相同位置且速度一致的重复状态,参考 力扣

8、Leetcode 1202

    a) 在并查集查找后额外进行路径压缩,从而保证相同集合的均使用同一代表值

    b)  使用二次遍历法统计每个集合的元素 (第一次统计各个集合元素个数,使用前缀和下标预留位置 第二次遍历将各个数字存入到相应的位置)

9、LeetCode Offer 42 连续子数组的最大和 并不是使用划窗,而是应该贪心

       划窗解决的问题是 子区间 和不能超越每个阈值时的最长区间类似的,也就是目标值区间和是给定的;显然,划窗类问题中左右边界将对答案有重要影响,而本题并不关心左右边界

10、部分场景中双端队列可以简化模拟优先队列,但两者是不同的。优先队列中存储所有压入的数值,而双端队列为了保证单调性,必须去重部分结点

Leetcode 407 接雨水中每个最小值还关联坐标信息,也就是相应的最小值仅对当前坐标周围点起效,因此不能简化为双端队列

Leetcode 239 滑动窗口中的最大值显然很容易通过优先队列实时获取(常规思路),为什么能简化为双端队列呢?就是因为非最大值的丢失不会影响计算结果,因此双端队列中单调性来模拟优先队列获取最大值

11、实时的票数最大值为上次的最大值和当前新更新的值比较,参考leetcode 911力扣

if (top < 0 || voteCounts[persons[i]] >= voteCounts[top]) {
   top = persons[i];
}
obj->tops[i] = top;

12、双指针将枚举的时间复杂度从 O(N^2)减少至 O(N)O(N), 适用场景:当我们需要枚举数组中的两个元素时,如果我们发现随着第一个元素的递增,第二个元素是递减的,那么就可以使用双指针的方法,参考leetcode 14力扣可以替代hash操作

13、Leetcode 415中两个数相加,由于很难事先确认相加后结果的有效位数,因此可以采取的方法是先下标小的位存放低位的结果,完成计算后数据进行翻转即可

14、Leetcode 630 中使用qsort模拟实现大小堆,力扣

void push_q(int val, int *q, int *tail) {
    q[(*tail)++] = val;
    // 剪枝, 仅在压入更大值时进行排序,减少排序次数
    if ((*tail) >= 2 && val < q[(*tail) - 2]) {
        qsort(q, (*tail), sizeof(int), _cmp);
    }
}

int pop_q(int *q, int *tail) {
    if ((*tail) == 0) {
        return 0;
    }

    return q[--(*tail)];
}

int top_q(int *q, int *tail) {
    if ((*tail) == 0) {
        return 0;
    }
    return q[(*tail) - 1];
}

15、146. LRU 缓存机制 利用uthash 双向链表的性质,访问之前先删除再添加,保证哈希表元素是按照访问时间从旧到新的,这样在删除“最久未使用”元素时只需要迭代哈希表,然后删除第一个元素即可

16、字符串的hash计算方法,参考力扣

我们可以用一个质数 p ,比如 31 当作底数; 将字符串转化为 sub[0]*p^{m-1}+sub[1]*p^{m-2}...+sub[m-1]sub[0]∗p ^m−1+sub[1]∗p^m−2...+sub[m−1] 。 这其实基本上就是 JDK 中对 string 哈希的默认做法
而这个哈希计算在滑动过程中,我们也不需要每次都重新计算一遍,可以用上一位置的状态转移过来,如果将hash值看成31进制数的话就是所有位数都左移一位,再去头加尾即可,相信很好理解:hash = hash * prime - prime^m * (s[i-len] - charCode.at(a)) + (s[i] - charCode.at(a))hash=hash∗prime−prime^m∗(s[i−len]−charCode.at(a))+(s[i]−charCode.at(a));
这就是滑动窗口的威力。

正常来说这个值会很大可能会导致溢出,所以RK算法应该还要对这个数取模,这样会导致hash冲突;不过用 unsigned long long 存储相当于自动取模

17、约瑟夫环,参考力扣

假设 f(n-1, m) = x, 显然 f(n, m) = (x+m)%n; 边界f(1,m)=0 表示只有一个元素的圈时,最后剩下的必然是下标0

18、排列数和组合数的差异参考力扣

比如爬楼梯中,如果楼梯3格,2+1和1+2 我们认为是两者不同的方法,则是排列数;而硬币面额 2+1 和1+2我们认为是相同的情况,则属于组合

先枚举金额,代表对于金额来说每次硬币循环都是可以的,1+2和2+1代表了两种方案,是排列数,也就是爬楼梯方案,但是先枚举硬币,那么只是对相应的金额进行方案添加,之前的硬币使用后不能再次使用1+2可行,但是2+1就不会再出现,是组合数,也就是零钱兑换的方案

19、双指针和滑动窗口的概念区分

计算过程仅与「两端点」相关的称为「双指针」,将计算过程与「两端点表示的区间」相关的称为「滑动窗口」,参考力扣 

20、对于16进制的编码,数字 0-9 的编码[48,58], 也就是荣通过 value |= 0x20 后仍旧属于[48,58], 这样就可以将 0x12aAd 统一为0x12aad 这样的字符串,仍旧是十六进制值

21、线段树(类似字典树)参考力扣,关键在于区间和的计算

1.上述代码构建的树应是1棵完全二叉树,配图是错的。叶子节点存储原始nums
2.根节点的索引是1
3.左子树节点的索引为偶数  右子树节点的索引为奇数
4.叶子节点的索引范围[n,2n-1]
int sumRange(int left, int right) {
        int sum = 0;
        left += len;
        right += len;
        while (left <= right) { // 左右区间更新规则在奇、偶上相反
            if (left % 2 == 1) {  
                sum += segmentTree[left++];
            }
            if (right % 2 == 0) {
                sum += segmentTree[right--];
            }
            left >>= 1;
            right >>= 1;
        }
        return sum;
    }

22、LeetCode 308,将每行看成一个子区间,则更新一个值时,仅仅更新相应行的前缀和(不需要更新整体二维区间的前缀和),问题简化成304

23、使用#define printf(...)屏蔽打印信息,确认超时是否因为printf引起

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值