学习笔记——大厂真题训练与解读_微软真题

微软真题

一、最长回文子串问题

题目描述:给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例1:
输入:“babad”
输出:“bab”
注意:"aba"也是一个有效答案

示例2:
输入:“cbbd”
输出:“bb”

命题关键字:字符串、动态规划

LeetCode地址: 5. 最长回文子串

解题思路

题干中给出“最长”,表明这是一道“求最值”的问题。基于之前的学习累积的经验以及修言大佬的总结——看到最值,就直接将动态规划拿出来。
由题可知,参数是一个字符串序列,它符合“序列型”动态规划的特征,对于序列型动态规划,我们总是需要以它的索引为线索去构造一维或二维的状态数组。对于本题来讲,由于定位任意子串需要的是两个索引,一个头部,一个尾部。因此我们的状态数组应该是一个二维数组:

// 初始化一个二维数组
let dp = []
// 给定字符串的长度
const len = s.length
for (let i = 0; i < len; i++) {
	dp[i] = []
}

对于判定字符串是否为回文串:
我们直接从两边开始,i 和 j 表示子串的两个端点。因为只要确认了这两个值,我们便可得到子串的长度。

"babad" 

b a b a d
i   j

以以上为例:

如果 s[i] === s[j]
	我们只需要判断 s[i+1] 和 s[j-1] 是否相等。
		如果相等,说明该子串为回文子串
			则 dp[i][j] = true
		如果不相等,说明不是
			则 dp[i][j] = false
	
如果 s[i] !== s[j],则 dp[i][j] = false

到这里,状态转移方程也就出来了,代码如下:

if(s[i] === s[j]) {
    dp[i][j] = dp[i+1][j-1]  
} else {
    dp[i][j] = false
}

状态转移方程出来后,我们就只差边界了。
我们可以思考一下,上面说到 s[i] === s[j],则需要比较 s[i+1] 和 s[j-1], 如果一直相等,那么必定会使得 i === j 。这个时候的 s[i] 和 s[j] 代表着同一个字母,而一个单词必定是回文串,因此,dp[i][i] = true

编码实现

/**
 * @param {string} s
 * @return {string}
 */
var longestPalindrome = function (s) {
    const dp = []
    // 给定字符串的长度
    const len = s.length
    // 初始化二维数组
    for (let i = 0; i < len; i++) {
        dp[i] = []
    }

    // 初始化最长回文子串的两个端点值
    let st = 0, end = 0
    // 处理一个字母字符串
    // 初始化单个字母的回文子串,也是边界
    for (let i = 0; i < len; i++) {
        dp[i][i] = true
    }
    // 处理两个字符的字符串
    // 这里为了降低题目的复杂度,作者预先对悬念比较小的 s[i][i + 1] 也做了处理。将两个相连的相同的字母所对应的 dp[i][i + 1] 设为 true
    for (let i = 0; i < len - 1; i++) {
        if (s[i] === s[i + 1]) {
            dp[i][i + 1] = true
            st = i
            end = i + 1
        }
    }
    // n 代表子串的长度,从3开始递增
    for (let n = 3; n <= len; n++) {
        // 下面的两层循环,用来实现状态转移方程
        for (let i = 0; i <= len - n; i++) {
            // 字符串最后一个索引
            let j = i + n - 1
            if (dp[i + 1][j - 1]) {
                // 若定位到更长的回文子串,则更新目标子串端点的索引值
                if (s[i] === s[j]) {
                    dp[i][j] = true
                    st = i
                    end = j
                }
            }
        }
    }
    return s.substring(st, end + 1)
};

小伙伴们可能会对最后一段代码有些疑惑

    // n 代表子串的长度,从3开始递增
    for (let n = 3; n <= len; n++) {
        // 下面的两层循环,用来实现状态转移方程
        for (let i = 0; i <= len - n; i++) {
            // 当前字符串最后一个索引
            let j = i + n - 1
            if (dp[i + 1][j - 1]) {
                // 若定位到更长的回文子串,则更新目标子串端点的索引值
                if (s[i] === s[j]) {
                    dp[i][j] = true
                    st = i
                    end = j
                }
            }
        }
    }

我们用案例来模拟一下

"caacbabad"

c a c b a b a d

准备操作,初始化
对于单个字母 如 "c"
dp[i][i] = true
相连的两个相等的字母 如 "aa"
dp[i][i+1] = true

如果字符串s的长度不为1,也不为2
才会继续进入最后一段代码
否则直接输出答案即可,小伙伴们可以细细品味一下这句话

接下来进入正题
c a c b a b a d

len = 8
初始 n = 3 ,最终 n <= len,当前 n <= 8
i = 0,且 i <= len - n,当前 i <= 5
j = i + n - 1 ,这里 j = 2

0 1 2 3 4 5 6 7
c a c b a b a d
i   j

记住这里 n = 3,以三个字符横移
开始 i 循环 i <= 5

第一次 i = 0,j = 2
c a c b a b a d
i   j
  i
  j
因为 i+1 === j-1 => 所以 dp[i+1][j-1] = true
又因为 s[i] === s[j] => 于是 dp[i][j] = true
st = 0,end = 2

第二次 i = 1,j = 3
c a c b a b a d
  i   j
    i
    j
因为 i+1 === j-1 => 所以 dp[i+1][j-1] = true
可是 s[i] !== s[j] => 于是 dp[i][j] = false
st = 0,end = 2

第三次 i = 2,j = 4
c a c b a b a d
    i   j
      i
      j
      
第四次 i = 3,j = 5
c a c b a b a d
      i   j
        i
        j
        
第五次 i = 4,j = 6
c a c b a b a d
        i   j
          i
          j
          
第六次 i = 5,j = 7
c a c b a b a d
          i   j
            i
            j

我们可以发现,n = 3 的 i 循环结束后
形成了一个列表
对角线为 i = j
对角线下面第一条为 i = i+1
对角线下面第二条为 n = 3
|j\i| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 0 | 1 |   |   |   |   |   |   |   |
| 1 | 0 | 1 |   |   |   |   |   |   |
| 2 | 1 | 0 | 1 |   |   |   |   |   |
| 3 |   | 0 | 0 | 1 |   |   |   |   |
| 4 |   |   | 0 | 0 | 1 |   |   |   |
| 5 |   |   |   | 1 | 0 | 1 |   |   |
| 6 |   |   |   |   | 1 | 0 | 1 |   |
| 7 |   |   |   |   |   | 0 | 0 | 1 |

经过全部循环后
表的左下半部分都会填满
越是靠左下部分,回文子串的长度越大
而 1 则代表着有效(true)
由 st 与 end 记录

最后返回答案 s.substring(st, end + 1)

二、从前序(先序)与中序遍历序列构造二叉树

待更…

三、复制带随即指针的链表

待更…


文章会持续更新,小伙伴们可以收藏一下

算法道路并不好走,希望各位能继续坚持。

算法小册子出自这里,有兴趣的小伙伴可以购买一起学习哦

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值