LeetCode第二周

233 阅读4分钟

回文数

判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

示例 1:

输入: 121

输出: true

输入: -121

输出: false

解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。

难易度:容易

解题思路

  1. 转换为字符串操作
var isPalindrome = function (x: number): boolean {
    if (x === 0) return true
    if (!x || x < 0) return false

    let rev = +(x + '').split('').reverse().join('')

    return x == rev
};
  1. 进阶,不操作字符串,反转一半数字

    按照题目,回文数指:如果数字的位数是奇数,那么去除掉中间位数的数字,前一半数字,等于后一半数字的反转;如果数字的位数是偶数,那么前一半数字,等于后一半数字的反转。

    所以,只需要反转一半的数字与原数字的前一半数字进行比较即可

    1223221反转

    每一次反转,反转数rev都加上数x的个位数,x则应该舍掉个位数,直到rev >= x,从下表可看出rev < x代表反转还没有进行到一半

    次数/值xrev
    11223221
    21223212
    31223122
    41221223
var isPalindrome2 = function (x: number): boolean {
    // x < 0  或者 x的个位数是0都不满足(x % 10)
    if (x === 0) return true
    if (!x || x < 0 || x % 10 == 0) return false

    let rev = 0 // 存储已反转的数

    // 结束点是rev < x  只要x > rev 那就说明这个数还没有反转到一半
    while (rev < x) {
        rev = rev * 10 + x % 10 // 每一次都取x的个位数加在反转结果上
        x = Math.floor(x / 10) // 每一次去掉x的个位数
    }
    // 如果x的位数是基数位,则最终结果 rev位数的肯定比x多一位,
    // rev的个位为原x的中间位数字
    // rev / 10 取整丢掉个位数再比较
    return x == rev || Math.floor(rev / 10) == x
};

Z字形变换

将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。 比如输入字符串为 "LEETCODEISHIRING" 行数为 3 时,排列如下:

LCIR
ETOESIIG
EDHN

之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"LCIRETOESIIGEDHN"。

难易度:中等

解题思路

Image

从上图可以看出

  1. 当笔向下画的时候,依次向每一行添加一个字符串,最大行numRows - 1

  2. 向右画的时候,依次向上一行添加一个元素,直到画到第0行

用一个标识downFlag,来表示是不是向下画,sIdx表示在第几行添加字符串,临界点就是sIdx == 0 || numRows - 1 == sIdx;用一个数组来装所有行,sIdx就为数组的下标

所以数组结构就是这样

    [
        '', // 第0行
        '', // 第1行
        '' // 第2行
    ]
    // 向下画 依次向[sIdx]添加一个字符串 sIdx += 1
    [
        'L',
        'E',
        'E',
    ]
    // 向右画 此时往[sIdx - 1]添加一个字符串 所以sIdx -= 1
    [
        'L',
        'ET',
        'E,'
    ]

var convert = function (s: string, numRows: number): string {
    const len = s.length
    if (!s || len <= numRows || numRows == 1) return s

    // 构建一个numRows个元素的数组,用来装所有拼接字符串
    const strArr = []
    for (let i = 0; i < numRows; i++) {
        strArr[i] = ''
    }

    let sIdx = 0 // 需要给strArr的第几个元素加上字符
    let downFlag = false // 是否是向下

    // 根据规律 向下画时,依次向所有行添加一个字符串,画到第numRows - 1行 往右画
    // 向右画时,依次向上一行画一个元素,划到第0行又往下画
    for (let i = 0; i < len; i++) {
        strArr[sIdx] += s[i]
        if (sIdx == 0 || sIdx == numRows - 1) downFlag = !downFlag

        sIdx = downFlag ? sIdx + 1 : sIdx - 1
    }

    // 拼接结果字符串
    let res = ''
    strArr.forEach(it => res += it)
    return res
};

最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀,返回空字符串 ""

·示例 1:

输入: ["flower","flow","flight"]

输出: "fl"

·示例 2:

输入: ["dog","racecar","car"]

输出: ""

解释: 输入不存在公共前缀。

所有输入只包含小写字母 a-z 。

难易度:容易

解题思路:

  1. 深度遍历

用字符串数组的第0位,与数组之后的所有位进行比较。

  • 第一遍循环,将strs[0]strs[1]进行比较,找出这两个字符串的最长公共前缀

    • 假设strs[0]strs[1]的子串
    • 如果strs[1].indexOf(strs[0]) == 0,说明str[1]和strs[0]的最长公共前缀就是strs[0]
    • 利用这个最长公共前缀,与strs[2]继续比较,找出最长公共前缀
    • 如果strs[1].indexOf(strs[0]) != 0,就删除str[0]的最后一个字符,再继续比较
  • 上述情况的结束点就是str[0]经过截取后只剩空字符串,说明他们没有公共前缀;strs数组循环完毕,那么经过截取后的str[0]就是最长公共前缀

const longestCommonPrefix = function (strs: string[]): string {
    const len = strs.length
    if (!len) return ''
    if(len == 1) return strs[0]

    // 找出字符串数组的第一位,用它和后面所有字符串比较
    let s = strs[0], index = 1

    while(index < len) {

        // 找到s 和 后一个字符串的 最长的公共前缀
        // 保存这个前缀
        // 继续用这个前缀和 再后一个字符串相比,找到最长的公共前缀
        // 直到公共前缀为空,或者遍历完整个字符串数组

        // 如果s被 strs[index]整个包含,说明s就是这两个字符串的最长公共前缀,直接进入下一次循环
        // 如果s已经只剩空字符串,跳出循环
        while(strs[index].indexOf(s) && s.length){
            // 否则,每次丢弃s最后一个字符,再进行比较
            s = s.substring(0, s.length - 1)
        }

        index++
    }
    // 最后 s 就最最长公共前缀
    return s
};
  1. 指针

看一遍题目,就应该想到的方法,把所有字符串的每一位字符,从第0位开始直接进行比较。

那么结束点就是遇到了不完全相等的字符,或者遍历完了最短的字符串,所以,这里可以先把最短的字符串找到。

Image

const longestCommonPrefix2 = function (strs: string[]): string {
    const len = strs.length
    if (!len) return ''
    if (len == 1) return strs[0]

    let minStr = strs[0]
    // 找到最短字符串
    for (let i = 1; i < len; i++) {
        if (minStr.length > strs[i].length) {
            minStr = strs[i]
        }
    }

    let index = 0 // 指针
    let temp = '' // 最长公共前缀

    // 遍历最短字符串
    for (const s of minStr) {
        // 遍历数组
        for (let i = 0; i < len; i++) {
            // 出现不相同的字符
            if (strs[i][index] != s) {
                return temp
            }
        }
        temp += s
        index++
    }

    return temp
}

整数转罗马数字

罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。

字符数值
I1
V5
X10
L50
C100
D500
M1000

例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做  XXVII, 即为 XX + V + II 。

  • 特殊情况

    I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。

    X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。 

    C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。

给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内

  • 示例:

    输入: 3

    输出: "III"

难易度:中等

解题思路:

数字的千位数 = 罗马字符的千位数里面寻找千位数值 相加 数字的百位数 = 罗马字符的百位数里面寻找百位数值 相加

从高位到低位相加(从大数字到小数字相加) ...

示例: 3459 = 1000 + 1000 + 1000 + 400 + 50 + 9

    => M + M + M + CD + L + IX = MMMCDLIX

3668 = 1000 + 1000 + 1000 + 500 + 100 + 50 + 10 + 50 + 10 + 10 + 10

    => M + M + M + D + C + L + X + V + I + I + I = MMMDCLXVIII
var intToRoman = function (num: number): string {
    if (!num) return ''

    // 创建数字和罗马数对应的字典 对象是无序的,所以这里还是用数组来存
    const keys: number[] = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
    const values: string[] = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]

    let res = ''
    for (let i = 0, len = keys.length; i < len; i++) {
        while (num >= keys[i]) {
            res += values[i]
            num -= keys[i]
        }
    }
    return res
};

最长回文子串

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

示例 1:

输入: "babad" 输出: "bab" 注意: "aba" 也是一个有效答案。

难易度:中等

解题思路

  1. 暴力解法

如果一个字符串倒置,等于原字符串,那么这个字符串就是回文串,所以只需要把给定字符串的,每一个子串进行是不是回文串的判断即可,但是这个方法性能很低,字符串长度达到一定程度,就会超时。

// 检查一个字符串是不是回文串
const reverseStr = function (s: string): boolean {
    let sarr = s.split('')
    let rs = sarr.reverse().join('')
    return rs == s
}

var longestPalindrome = function (s: string): string {
    if (!s) return ''
    if (s.length == 1 || reverseStr(s)) return s

    let temp = ''
    let res = ''
    let ss = s
    // 两层循环,找出所有子串
    while (ss.length) {
        for (let i of ss) {
            temp += i
            if (reverseStr(temp)) {
                res = res.length > temp.length ? res : temp
            }
        }

        temp = ''
        ss = ss.substr(1)
    }

    return res
};
  1. 向两边扩散法

找定一个字符串,以这个字符为中心,向两边扩散,如果两边的字符串相等,继续向两边扩散,直到两边不相等,那么中间的字符串就是回文串;

以字符串长度为标准,多次遍历,找出其中最长的字符串,就是答案

var longestPalindrome2 = function (s: string): string {
    if (!s) return ''
    
    let len = s.length
    if (len == 1) return s

    let res = s[0];

    for (let i = 0; i < len; i++) {
        for (let j = 1; j <= 2; j++) { //偶数奇数回文串
            let left = i, right = i + j;
            while (left >= 0 && right < len && s[left] === s[right]) {
                left--, right++; //向外扩展直到两端不相同
            };
            let length = right - left - 1; //(right - 1) - (left + 1) + 1
            if (length > result.length) {
                res = s.substr(left + 1, length);
            }
        }
    }
    return res;
}