返回

站在巨人的肩膀上:揭秘Go语言中LeetDay0075的精妙

后端

打家劫舍 II:在窃取财富与安全逃脱间寻求平衡

想象一下,你是一位经验丰富的窃贼,你的目标是一条富丽堂皇的街道,每家每户都装满了珍贵财宝。然而,有一个不容忽视的障碍:你不能连续抢劫相邻的两家,否则必定会引起警方的注意。在这个充满诱惑和危险的场景中,你该如何规划你的偷盗行动,才能收获最大战利品,又不被抓获呢?

在“打家劫舍 II”难题中,动态规划算法将成为你的得力助手。它能将问题分解成一系列子问题,并逐步解决,最终汇聚成整体解决方案。具体来说,你可以将房屋编号为 1 到 n,并定义一个 dp 数组,其中 dp[i] 表示抢劫到第 i 户人家的最大收益。

那么,递推公式可以写成:

dp[i] = max(dp[i-1], dp[i-2] + nums[i])

这意味着,你当前的收益要么来自上一户人家的收益,要么来自前两户人家的收益加上当前户的收益。如此一来,你可以从前往后依次计算出每个位置的最大收益,并最终得到整个街区的最大收益。

func rob(nums []int) int {
    n := len(nums)
    if n == 1 {
        return nums[0]
    }
    dp := make([]int, n)
    dp[0] = nums[0]
    dp[1] = max(nums[0], nums[1])
    for i := 2; i < n; i++ {
        dp[i] = max(dp[i-1], dp[i-2]+nums[i])
    }
    return max(dp[n-1], dp[n-2])
}

最短回文串:寻找字符串中的隐藏对称

回文串,那些从左往右读和从右往左读都一样的字符串,就像语言中的镜子,折射出一种独特的对称美感。而在“最短回文串”难题中,你的任务是找到一个字符串中包含的最短回文子字符串。

解决这道难题的关键在于后缀数组,一种专门为字符串设计的数据结构。它能帮助你快速查询一个字符串的所有后缀,从而快速找到最短的回文子字符串。

具体实现中,你可以使用 Manacher 算法构建后缀数组,并利用最长公共前缀(LCP)数组来计算字符串中所有回文子串的长度。通过不断更新 LCP 数组,你最终可以找到最短的回文子字符串。

func shortestPalindrome(s string) string {
    n := len(s)
    if n == 0 {
        return ""
    }
    sa := buildSuffixArray(s)
    lps := buildLpsArray(s)
    i := 0
    j := 0
    for i < n {
        if s[i] == s[j] {
            i++
            j++
        } else if j > 0 {
            j = lps[j-1]
        } else {
            i++
        }
    }
    prefix := s[j:]
    reversePrefix := reverse(prefix)
    return reversePrefix + s + prefix
}

func buildSuffixArray(s string) []int {
    n := len(s)
    sa := make([]int, n)
    for i := 0; i < n; i++ {
        sa[i] = i
    }
    sort.Slice(sa, func(i, j int) bool {
        return s[sa[i]:] < s[sa[j]:]
    })
    return sa
}

func buildLpsArray(s string) []int {
    n := len(s)
    lps := make([]int, n)
    lps[0] = 0
    i := 1
    j := 0
    for i < n {
        if s[i] == s[j] {
            lps[i] = j + 1
            i++
            j++
        } else if j > 0 {
            j = lps[j-1]
        } else {
            lps[i] = 0
            i++
        }
    }
    return lps
}

func reverse(s string) string {
    r := []rune(s)
    for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r)
}

常见问题解答

1. 我需要具备哪些知识才能理解这些算法?

解决这些难题需要对动态规划、后缀数组和最长公共前缀算法有基本的了解。

2. 这些算法在实际中有什么应用?

打家劫舍算法可用于解决各种优化问题,如任务调度和库存管理。最短回文串算法可用于文本处理、数据压缩和密码学。

3. 如何提高解决这些难题的能力?

练习是关键。尝试解决各种难题,并分析不同的解法。

4. 还有哪些类似的算法?

其他常见的动态规划算法包括最长公共子序列、背包问题和旅行商问题。

5. 这些算法在编程语言中如何实现?

提供的代码示例展示了如何在 Go 语言中实现这些算法。