(24)Go递归求组合类问题

问题1:求电话号码的字母组合 //

这是一个树类问题,可借组树天然的递归性质求解,结构如下图:


// 字符串查询表
var m []string = []string{
    "abc",
    "def",
    "ghi",
    "jkl",
    "mno",
    "pqrs",
    "tuv",
    "wxyz",
}

// 时间复杂度为3^n=O(2^n)
func letterCombinations(digits string) []string {
    length := len(digits)
    if length == 0 {
        return nil
    }
    res := []string{}
    combination(digits, length-1, 0, &res, "")

    return res
}

// 深度优先遍历方式,从上而下添加字符方式
func combination(digits string, endI int, index int, res *[]string, str string) {
    // 递归终止条件
    if index == endI {
        // 每次遍历到底部,在底部把str加到res里面
        for _, v := range m[digits[index]-'2'] {
            *res = append(*res, str+string(v))
        }
    } else {
        // 递归过程
        for _, v := range m[digits[index]-'2'] {
            // 每次遍历到底部,在底部把str加到res里面
            combination(digits, endI, index+1, res, str+string(v))
        }
    }
}

提交leetcode,通过

问题2:求全排列 //

一样是树问题,树的结构如下图示:


递归结构:perms(nums[0...n-1]) = {取出1个数字} + perms(nums[{0...n-1} - 这个数字])

func permute(nums []int) [][]int {
    n := len(nums)
    if n == 0 {
        return nil
    }

    res := [][]int{}           //组合集合
    arr := make([]int, n)      //组合
    visited := make([]bool, n) //是否已经访问过
    flashBack(nums, 0, n, visited, arr, &res)
    return res
}

// 深度优先遍历方式,自上而下添加元素
// 向arr[0...index-1]末尾添加第index位元素,获得arr[0...index]元素的组合
func flashBack(nums []int, index, n int, visited []bool, arr []int, res *[][]int) {

    // 递归终止条件
    if index == n {
        // 因为arr加在res里面后,arr修改,res里面也会被修改
        // 所以这里做1次复制
        buf := append([]int{}, arr...)
        *res = append(*res, buf)
    } else {

        // 递归过程
        for i := 0; i < n; i++ {
            if !visited[i] {
                visited[i] = true
                // 推进元素,用完不同推出元素,下次循环重新覆盖
                arr[index] = nums[i]
                flashBack(nums, index+1, n, visited, arr, res)
                // 用完重置为false,下次循环得用
                visited[i] = false
            }
        }
    }
}

提交leetcode,通过

问题3:求全排列2,和问题2对比 //

由上图可以看出,每次取出元素i后,只需从i之后的区间寻找下一个可填充的元素,即在[i+1,n]区间里寻找

方法1:未优化版本,理解未优化版本有助于理解优化版本
func combine(n int, k int) [][]int {
    if n <= 0 || k > n || k <= 0 {
        return nil
    }

    res := [][]int{}      //组合集合
    arr := make([]int, k) //长度k的组合
    generateCombinations(n, k, 0, 1, arr, &res)
    return res
}

// index: arr[index]位元素赋值
// start: 从[start...n]开始寻找下一个可以填充的元素
// 函数功能:从[start...n]里寻找下一位可以填充的元素,填充在arr[index]位置
func generateCombinations(n, k, index, start int, arr []int, res *[][]int) {

    // 递归终止条件
    if index == k {
        // 做一次复制
        buf := append([]int{}, arr...)
        *res = append(*res, buf)
    } else {
        // 递归过程
        for i := start; i <= n; i++ {
            arr[index] = i
            generateCombinations(n, k, index+1, i+1, arr, res)
        }
    }
}

方法1提交leetcode,通过

优化:arr[0...index-1]的元素已经填充,剩下k-index个空位,
因此[start...n]至少要有k-index个元素
即 k-index<=n-start+1,推导出 start<=n+1-(k-index)

// 优化1
func generateCombinations2(n, k, index, start int, arr []int, res *[][]int) {

    // 递归终止条件
    if index == k {
        // 做一次复制
        buf := append([]int{}, arr...)
        *res = append(*res, buf)
    } else {
        // 优化:arr[0...index-1]的元素已经填充,剩下k-index个空位,
        // 因此[start...n]至少要有k-index个元素
        // 即 k-index<=n-start+1,推导出 start<=n+1-(k-index)

        // 递归过程
        for i := start; i <= n+1-(k-index); i++ {
            arr[index] = i
            generateCombinations2(n, k, index+1, i+1, arr, res)
        }
    }
}

在优化1的基础上进一步优化:每次arr加1个元素,令k-1,即剩下k-1个位置,
递归终止条件变成k==0
func generateCombinations3(n, k, index, start int, arr []int, res *[][]int) {
    if k == 0 {
        buf := append([]int{}, arr...)
        *res = append(*res, buf)
    } else {
        // 每次arr加1个元素,k-1,即剩下k-1个空位置
        for i := start; i <= n+1-k; i++ {
            arr[index] = i
            generateCombinations3(n, k-1, index+1, i+1, arr, res)
        }
    }
}

方法3提交leetcode,通过

有bug欢迎指出

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