实战算法策略

这个视频给我最大的感触就是算法模板。以前大概能感觉出来很多问题有相似之处,但是没有想过是不是有模板。用回溯递归为例,我翻了一下N-Queues问题和Subsets问题的递归解法比对一下,确实相似的地方很多。至于为什么当初没有想过算法模板,洞察力不够可能是一方面,另一方面应该要属于算法答案的多重性。对于同一类型的问题,给人第一感觉的解法可能是不一样,比如A题我先想到了一个递归解法,B题我一下想到了非递归的解法,虽然是同一类型这样却找不到共同点了。

有了模板,很多事就轻松多了。就像我渣渣的英语,因为有作文模板的存在,水平是次了点,但作文是分丢不了。不知道后面董老师会给多少模板,肯定是越完备越好了,还是比较期待的。当然,对于基础题目的熟练掌握是必须的,这个无需解释的。没有基础,再多的模板也算是白搭。

首先董老师给出个实战算法的策略:
1. 相似题目
- 考察算法的类型实际上不多,应该多总结。
- 关注质多于量,对做过的题目能完美的解决。
2. 找出适合同一类题目的模板程序
- 类似于英语作文,其实同一类的题目也是有模板框架的。
- 以回溯递归的题目为例子,仔细点可以发现这些答案的共同点有很多
3. 对基础题熟练掌握

1.Memmove()

从基础题开始,以Memmove()为例,考察对基础的掌握程度。Memmove()算是经典老题了,它考察的关键点就在于有没考虑到内存重叠的处理情况。

void *memmove(void *dest, void const *src, size_t n)
{
    register char *dp = dest;
    register char const *sp = src;

    //通过dp和sp的比较,可以得出dp的哪端,确定是没有重叠的
    if (dp < sp) {
        while(n-- !=0)
             *dp++ == *sp++;
    } else {
        dp += n;
        sp += n;
        while (n-- != 0)
             *--dp = *--sp;
    }

    return dest;
}

2.无重复元素的Subsets()

接着是Subsets()问题,也就是求所有元素的子集。它的解法当然不止一种,起码有递归和非递归两种。这里是以递归为例子,它应该和N-Queues问题一样属于NP问题,其时间复杂度也不用怀疑了,肯定是指数量级的。
除了N-Queues,和Subsets同一类型的题目还有很多,比如:Combination Sum I & II,Permutations,Combination,数独……然而它们的解题思路都是相当一致的,中心思想都是用一个循环递归处理子问题。

以Subsets(没有重复元素)为例,它的核心问题在于某个元素到底是:放or不放,则先假设放入,进入子问题递推处理,之后再将其移除,具体代码如下:

void subsets(int [] num)
{
     ArrayList<Integer> path = new ArrayList<Integer>();
     Arrays.sort(num);
     subsetsHelper(path, num, 0); 
}
void subsetsHelper(ArrayList<Integer> path, int[] num, int pos)
{
     outputToResult(path);

     for (int i = pos; i < num.length; i++)
     {
          path.add(num[i]);
          subsetsHelper(path, num, i + 1);
          path.remove(path.size() - 1);
     }
}
  • sort是不是可以去掉?就这题来说,是的。但是如果数组中有重复元素呢,这时要么用sort,要么用set。这里加了一个sort进行排序,一方面可以使输出的结果从小到大排列看起来比较舒服,另一方面也能与之后复杂情况的代码保持一致性。
  • 关键代码就是subsetsHelper中的for循环。即首先假设num[i]放入到path中,然后子问题递归处理,最后将num[i]移除,继续循环。
  • {1,2,3}示意图如下。请注意输出的顺序。
    这里写图片描述

3.有重复元素的Subsets()

那么如果集合中有重复元素呢,仔细观察,可以发现:我们只关心取多少个某重复元素,而不关心取得是第几个。也就是对重复元素循环时跳过递归处理,只对第一个未处理的元素进行递归处理(当处理第一个递归时,会关注于重复元素的个数)。具体代码如下:

void subsets(int [] num)
{
     ArrayList<Integer> path = new ArrayList<Integer>();
     Arrays.sort(num);
     subsetsHelper(path, num, 0); 
}
void subsetsHelper(ArrayList<Integer> path, int[] num, int pos)
{
     outputToResult(path);

     for (int i = pos; i < num.length; i++)
     {
          if (i > 0 && i != pos && num[i]==num[i-1])
              continue;

          path.add(num[i]);
          subsetsHelper(path, num, i + 1);
          path.remove(path.size() - 1);
     }
}

没错,看起来简单的多了中间的if语句。我们分析一下,if中的要点吧:

  • sort是必须的。如果没有再前面使用sort,那么则需要使用set来判断是否已经重用了某元素。
  • i != pos 其实满巧妙的。它不仅能够使我们不关注于取得哪个元素,而且和for循环中初始值i = pos配合取得多次不同个数的相同元素情况。
  • {1,2,2}示意图如下。也请注意输出的顺序。
    这里写图片描述

最后,关于排列组合的问题,这个估计还能再写一篇,之后再填吧。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值