【LeetCode选讲·第三期】「最长回文子串」「Z字形变换」「回文数」

167 阅读3分钟

T5 最长回文子串

题目链接:leetcode.cn/problems/lo…

思路:模拟法

遍历整个字符串,假定指针所指的是回文字符串的中心位置,再向两侧进行检测即可。别忘了分类讨论回文字符串总长度是奇数还是偶数!

代码如下:

'use strict';
function longestPalindrome(str) {
    let maxLen = 0;
    let ans = '';
    let totalLen = str.length
    for(let i = 0; i < totalLen; i++) {
        /* 处理奇数回文串 */
        let s = getInfo(str, i - 1, i + 1, str[i]);
        s.length > ans.length && (ans = s);
        /* 处理偶数回文串 */
        s = getInfo(str, i, i + 1, '');
        s.length > ans.length && (ans = s);
    }
    return ans;
}
function getInfo(str, x, y, s) {
    let totalLen = str.length;
    while(x >= 0 && y < totalLen && str[x] === str[y]) {
        s = str[x] + s + str[y];
        x--;
        y++;
    }
    return s;
}

T6 Z字形变换

题目链接:leetcode.cn/problems/zi…

暴力模拟法

这是一道有关「矩阵」的「模拟」题

如果我们不考虑题目中要求的是输出拼接后的字符串,直接按题目中所谓的“Z字形变换”进行模拟的话,便可能得到如下的代码:

function convert(str, numRows) {
    /* 针对仅有一行的情况特殊处理 */
    if (numRows === 1) return str;
    /* 创建二维数组 */
    let ans = [];
    for(let i = 0; i < numRows; i++) ans[i] = [];
    /* 进行Z字形变换 */
    let curCol = 0;
    let curRow = 0;
    let isUp = false;
    for(let i = 0; i < str.length; i++) {
        ans[curRow][curCol] = str[i];
        //处理向下排列字母
        if (!isUp && curRow < numRows - 1) {
            curRow++;
        }
        //至底部时改变方向
        else if (!isUp) {
            isUp = true;
            curCol++;
            curRow = numRows - 2;
        } 
        //处理向右上方向排列字母
        else if (isUp && curRow > 0) {
            curRow--;
            curCol++;
        }
        //至顶部时改变方向        
        else if (isUp) {
            isUp = false;
            curRow++;
        }
    }
    /* 生成结果 */
    return ans.flat().join('');
}

但稍加思考,我们便能注意到——原题中所谓的变换实际上是个大忽悠。因为所求的拼接后的字符串是忽略空隙的,所以我们在编写代码时只需要考虑竖直方向的字符添加

故上述代码可以化简为:

function convert(str, numRows) {
    /* 针对仅有一行的情况特殊处理 */
    if (numRows === 1) return str;
    /* 创建二维数组 */
    let ans = [];
    for(let i = 0; i < numRows; i++) ans[i] = [];
    /* 进行Z字形变换 */
    let curRow = 0;
    let isUp = false;
    for(let i = 0; i < str.length; i++) {
        ans[curRow].push(str[i]);
        //处理向下排列字母
        if (!isUp && curRow < numRows - 1) {
            curRow++;
        }
        //至底部时改变方向
        else if (!isUp) {
            isUp = true;
            curRow = numRows - 2;
        } 
        //处理向右上方向排列字母
        else if (isUp && curRow > 0) {
            curRow--;
        }
        //至顶部时改变方向        
        else if (isUp) {
            isUp = false;
            curRow++;
        }
    }
    /* 生成结果 */
    return ans.flat().join('');
}

技巧:正负k法

即便我们通过对题意的分析,去除了对水平方向的考量,但前述代码中的四个if语句仍然使代码看上去比较冗长。能不能进一步化简我们的代码?

答案是肯定的。在做有关「矩阵」的题目时,有一个常用技巧:我们可以引入变量k = ±1,通过k正负变化来控制字符排列的上下方向。

代码如下:

function convert(str, numRows) {
    /* 针对仅有一行的情况特殊处理 */
    if (numRows === 1) return str;
    /* 创建二维数组 */
    let ans = [];
    for(let i = 0; i < numRows; i++) ans[i] = [];
    /* 进行Z字形变换 */
    let curRow = 0;
    let k = 1;
    for(let i = 0; i < str.length; i++) {
        ans[curRow].push(str[i]);
        //更新curRow
        curRow += k;
        //向下排列至底部时改变方向
        if (curRow === numRows) {
            k = -1;
            curRow -= 2;
        }
        //向上排列至顶部时改变方向        
        else if (curRow < 0) {
            k = 1;
            curRow += 2;
        }
    }
    /* 生成结果 */
    return ans.flat().join('');
}

借助「正负k法」,我们避免了对字符排列上下情况的分别处理,进一步简化了代码!

T9 回文数

题目链接:leetcode-cn.com/problems/pa…

思路一:定义法

利用回文串左右对称的定义,逐个取数验证即可。这种方法的缺点在于首先需要求解出传入数字的位数,再逐个取数验证,算法较为繁琐。

代码如下:

'use strict';
function isPalindrome(num) {
    //负数肯定不是回文数
    if(num < 0) return false;  
    //计算传入数据的位数
    let len = 0;
    let _num = num;
    while(_num >= 10) {
        _num /= 10;
        len++;
    }
    //检测是不是回文数
    let count = 0;
    //通过对len的处理就不用分类讨论数字长度的奇偶性了
    while(count < Math.floor(len / 2 + 0.5)) {
        let head = Math.floor(num / (10 ** (len - count))) % 10;
        let tail = Math.floor(num / 10 ** count) % 10;
        if(head !== tail) return false; 
        count++;
    }
    return true;
}

思路二:翻转法

完全翻转法

根据回文数的定义,将其完全翻转后,得到的数应该与原先是完全相等的。我们可以利用这个性质进行验证。

代码如下:

'use strict';
function isPalindrome(num) {
    if(num < 0) return false;  
    let t = 0;
    let _num = num;
    while(num > 0) {
        val = num % 10;
        t = t * 10 + val;
        num = Math.floor(num / 10);
    }
    return _num === t;
}

非完全翻转法

根据回文数的定义,我们选取其左半部分或右半部分进行翻转后,得到的数应该与另一部分是完全相等的。我们也可以利用这个性质进行验证。

我们可能会首先考虑先通过整除和取模运算分别取得要判断数字的前半部分和后半部分,再对其中一部分进行翻转。这么做便意味着需要先求解出数字的位数,代码仍然较为繁琐。

下面提供一种更加巧妙的做法:

'use strict';
function isPalindrome(num) {
    //对负数和无法使用半截法10^n型数字特殊处理
    if(num < 0 || num !== 0 && num % 10 === 0) return false;  
    let x = 0;
    while(num > x) {
        x = x * 10 + num % 10;
        num = Math.floor( num / 10 );
    }
    //情况一:偶数位回文串直接比较对半分的两部分
    //情况二:奇数位回文串舍去最中间的数,再比较两部分
    return num === x || Math.floor(x / 10) === num;
}

写在文末

我是来自在校学生兴趣小组江南游戏开发社的PAK向日葵,我们目前正在致力于开发自研的非营利性网页端同人游戏《植物大战僵尸:旅行》,以锻炼我们的前端应用开发能力。

我们诚挚邀请您体验我们的这款优秀作品,如果您喜欢TA的话,欢迎向您的同事和朋友推荐。如果您有技术方面的问题希望与我们探讨,欢迎直接与我联系。您的支持是我们最大的动力!

《植物大战僵尸:旅行》