Leetcode日记:46&47.排列组合与回溯(backtrack)

Leetcode日记:46&47.排列组合与回溯(backtrack)

回溯仿佛机器人走迷宫

46排列组合1

题目

Given a collection of distinct integers, return all possible permutations.

Example:


Input: [1,2,3]

Output:

[

  [1,2,3],

  [1,3,2],

  [2,1,3],

  [2,3,1],

  [3,1,2],

  [3,2,1]

]

分析

这道题目的很明确,就是要求出数组所有排列组合情况,重点是不重复的数字,也就是说我们并不用考虑重复排列的情况

代码1


public List<List<Integer>> permute(int[] nums) {

  List<List<Integer>> list = new ArrayList<>();

  // Arrays.sort(nums); // not necessary

  backtrack(list, new ArrayList<>(), nums);

  return list;

}

private void backtrack(List<List<Integer>> list, List<Integer> tempList, int [] nums){

  if(tempList.size() == nums.length){

      list.add(new ArrayList<>(tempList));

  } else{

      for(int i = 0; i < nums.length; i++){

        if(tempList.contains(nums[i])) continue; // element already exists, skip

        tempList.add(nums[i]);

        backtrack(list, tempList, nums);

        tempList.remove(tempList.size() - 1);

      }

  }

}

47排列组合2

问题

Given a collection of numbers that might contain duplicates, return all possible unique permutations.

Example:


Input: [1,1,2]

Output:

[

  [1,1,2],

  [1,2,1],

  [2,1,1]

]

代码2


public List<List<Integer>> permuteUnique(int[] nums) {

    List<List<Integer>> list = new ArrayList<>();

    Arrays.sort(nums);

    backtrack(list, new ArrayList<>(), nums, new boolean[nums.length]);

    return list;

}

private void backtrack(List<List<Integer>> list, List<Integer> tempList, int [] nums, boolean [] used){

    if(tempList.size() == nums.length){

        list.add(new ArrayList<>(tempList));

    } else{

        for(int i = 0; i < nums.length; i++){

            if(used[i] || i > 0 && nums[i] == nums[i-1] && !used[i - 1]) continue;

            used[i] = true;

            tempList.add(nums[i]);

            backtrack(list, tempList, nums, used);

            used[i] = false;

            tempList.remove(tempList.size() - 1);

        }

    }

}

回溯算法

步骤

用回溯算法解决问题的一般步骤:

  1. 针对所给问题,定义问题的解空间,它至少包含问题的一个(最优)解。

  2. 确定易于搜索的解空间结构,使得能用回溯法方便地搜索整个解空间 。

  3. 以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索。

基本思想

回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。比如走迷宫问题,就是这条路走不通,然后返回前一个路口,继续下一条路。回溯算法说白了就是穷举法。不过回溯算法使用剪枝函数,剪去一些不可能到达 最终状态(即答案状态)的节点,从而减少状态空间树节点的生成。

回溯与动态规划

这道题让我想起之前的爬楼问题,都是一个问题由一个或者几个子问题构成,不断划分

他们之间的区别在于:动态规划往往是寻找一个最优解,而回溯问题则是穷举,深度遍历所有情况,当然也存在不符合要求的情况,但是仍然要遍历到相应的节点上。

适用情况

  • 穷举问题,例如在某个范围内求所有可能情况

  • 搜索空间均可以表示成树的样子

用回溯思想解决问题

回到问题

排列组合1

首先,我们来看,这是一种不需要剪枝的问题,得到的所有解一定唯一,那我们看一看程序是怎么实现回溯的:

图示:

回溯算法图示
  1. 首先明确回溯是一个递归性质的问题,这道题模型是传一个待排列数组和原数组。

  2. 从0位置依次加入待排列数组,由于每次递归都要从0位置开始判断,所以传入之后要先判断这个数组是否已经出现过这个位置上的数,如果不曾出现,把这个数加入到数组中,再次递归。

  3. 直到依照这个方法将数组填满(tempList.size() == nums.length),把这样计算出来的数组加入到结果中。这样其中的一个分枝就完成了。

  4. 完成之后自然进行回溯(跳出该层递归),寻找下个排列组合。

排列组合2

这个问题和上面区别不是很大,首先关键点是先用sort函数将数组排序,使重复的元素都放在一起,下面主要说一下剪枝判断的设计

首先,用used[i]布尔型数组来判断是否该元素是否已经被排列组合过。

判断语句if(used[i] || i > 0 && nums[i] == nums[i-1] && !used[i - 1])

  1. 前一个used[i]相当与问题1中的判断,即当前待排列数组中是否包含了其本身(这种情况并不是重复,而是其本身);

  2. 后面i > 0 && nums[i] == nums[i-1] && !used[i - 1])其实想表明的意思是该元素与前面的元素重复,并且等于这个值的元素已被排列好;

这两种情况便可跳过排列组合,也就是剪枝

注意的问题


tempList.remove(tempList.size() - 1);

注意这个语句,由于我们待排列数组只有一个,我们不断更新,加入的标志是已满,如果得到第一种满足条件的情况直接返回,那么这个排列数组一直保持满的状态,不在会被更新,所以回溯的时候要注意,把带排列数组元素-1,才能有新排列组合情况进来。

其他问题也同样,如果维持的是同一个数据结构,注意回溯的时候回退数据结构中的内容。

总结

讲了那么多,我们需要注意回溯的几个关键要素

  1. 迭代

    回溯问题体现在程序设计上大多数是递归,而每一层递归又会有一个循环来遍历这层递归的所有情况

  2. 条件

    对于每个特定的解的某一步,他必然要符合某个解要求符合的条件,如果不符合条件,就要回溯,其实回溯也就是递归调用的返回。

  3. 结束

    当到达一个特定结束条件时候,就认为这个一步步构建的解是符合要求的解了。把解存下来或者打印出来。对于这一步来说,有时候也可以另外写一个issolution函数来进行判断。注意,当到达第三步后,有时候还需要构建一个数据结构,把符合要求的解存起来,便于当得到所有解后,把解空间输出来。这个数据结构必须是全局的,作为参数之一传递给递归函数。而且要注意:如果维持的是同一个数据结构,注意回溯的时候回退数据结构中的内容。

后面几天我会多找一些关于回溯算法的题来品尝。

更多关于回溯的问题

更多关于回溯算法的问题请转到回溯算法标签

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • <center>#1 Two Sum</center> link Description:Given an arr...
    铛铛铛clark阅读 2,135评论 0 3
  • LeetCode 刷题随手记 - 第一部分 前 256 题(非会员),仅算法题,的吐槽 https://leetc...
    蕾娜漢默阅读 17,676评论 2 36
  • 1.基本概念 回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件...
    RavenX阅读 8,234评论 1 2
  • 线性表中的双指针法是指通过两个指针(游标)来指示线性表中的元素的方法。双指针的使用本身并没有什么神奇之处,但是通过...
    Like_eb56阅读 510评论 0 0
  • <center>#51 N-Queens</center> link Description:The n-quee...
    铛铛铛clark阅读 978评论 0 0