算法学习记录(六十三)

100 阅读2分钟

问:

  1. 剑指 Offer 42. 连续子数组的最大和
  2. 剑指 Offer 44. 数字序列中某一位的数字
  3. 剑指 Offer 45. 把数组排成最小的数
  4. 剑指 Offer 46. 把数字翻译成字符串
  5. 剑指 Offer 47. 礼物的最大价值
  6. 剑指 Offer 48. 最长不含重复字符的子字符串

解:

  1. 设置变量计算累加和,如果之前的累加和小于0就代表之前的累加和是拖后腿的,直接重0开始计算累加和
const maxSubArray = function(nums) {
    let preSum = 0
    let max = -Infinity
    for (let i of nums) {
        preSum = preSum < 0 ? 0 : preSum
        preSum += i
        max = Math.max(max, preSum)
    }
    return max
};
  1. 0 ~ 9 有10个字符(特例,直接返回)。10 ~ 99 有 2 * 90个字符,100 ~ 999 有 3 * 900个字符, 1000 ~ 9999有 4 * 9000个字符...计算出n刚好被哪个区域包含, 以及从0到该区域最大值一共有多少个字符,也就知道了n到该区域最大值缺了多少字符。缺的字符个数除以所在区域的位数,就知道应该往前推多少个数字。如果还有余数,那么就在往前推一个数字找对应字符即可。
const findNthDigit = function(n) {
    if (n <= 9) return n
    // 前面已经压了多少个字符
    let preNums = 10
    // 当前是多少位(个、十、百的符号位的意思)
    let dig = 2
    // 计算出n刚好被哪个区域包含
    while (preNums <= n) {
        preNums += (dig * 9) * 10 ** (dig - 1)
        dig++
    }
    console.log(dig, preNums);
    // 假设n = 372
    // 通过计算得出dig = 4,所以n所属区域为dig - 1 也就是3位数,属于100~999
    // 计算出 0 ~ 999一共有多少个字符,减去372
    // 10 + 180 + 2700 - 372 = 2518  这就是第372位字符到 数字999中间有多少字符
    // 并且此时是3位数区间,所以2518 / 3 = 839 余 1
    // 也就是说第372位和999之间差了839个数字并且多了一个字符
    // 999 - 839 + 1 = 161,由于还多余了一个字符,所以往前推一个数字,就是取160的最后一个字符
    let needNum = 10 ** (dig - 1) - Math.floor((preNums - n) / (dig - 1 ))
    let remainder = (preNums - n) % (dig - 1 )
    if (remainder === 0) {
        needNum = '' + needNum
        return needNum[0]
    } else {
        needNum = '' + (needNum - 1)
        return needNum[needNum.length - remainder]
    }
};
const minNumber = function(arr) {
    return arr.sort((a, b) => (''+ a + b) - ('' + b + a)).join('')
};
  1. 假设i从0位置开始递归,当前位置的值是1或者是2并且2的下一位值小于6,那么这种字符有两种选择:①自己变;②带着下一个字符一起变。这两种情况就是i+1去递归和i+2去递归的累加。对于不是上述情况的字符而言,只能自己变,也就是i+1去递归。当i递归到str.length的时候,表明转换已经结束,返回一种方案。越界则返回0种方案。
// 递归改dp
const translateNum = function(str) {
    str = '' + str
    const dp = []
    dp[str.length] = 1
    for (let i = str.length - 1; i >= 0; i--) {
        if (str[i] === '1' || (str[i] === '2' && str[i + 1] && str[i + 1] < '6')) {
            dp[i] = dp[i + 1] + (dp[i + 2] ?? 0)
        } else {
            dp[i] = dp[i + 1]
        }
    }
    return dp[0]
};
  1. 假设从右下角开始dp。对于最后一行来说每个位置只依赖于右边的值,对于最后一列来说每个位置只依赖于下边的值。对于普遍位置来说取下方和右方较大的那个。从下往上从右往左填表即可。
const maxValue = function(grid) {
    const dp = []
    for (let i = grid.length - 1; i >= 0; i--) {
        dp[i] = []
        for (let j = grid[0].length - 1; j >= 0; j--) {
            if (i === grid.length - 1 && j === grid[0].length - 1) {
                dp[i][j] = grid[i][j]
                continue
            }
            if (i === grid.length - 1) {
                dp[i][j] = dp[i][j + 1] + grid[i][j]
            } else if (j === grid[0].length - 1) {
                dp[i][j] = dp[i + 1][j] + grid[i][j]
            } else {
                dp[i][j] = Math.max(dp[i + 1][j] , dp[i][j + 1]) + grid[i][j]
            }
        }
    }
    return dp[0][0]
};
  1. dp表的含义为 每个以当前字符串为结尾时,它的最大长度是多少。可以分析出对于每个位置而言,它的最大长度受到两个因素的影响。一是这个字符上次出现在什么位置,二是当前字符的前一个字符能达到的最大长度是多少。这两个都是当前字符的瓶颈,所以只能取更小的值。
const lengthOfLongestSubstring = function(str) {
    if (!str) return 0
    // 记录字符出现的位置
    const idxMap = new Map()
    // 每一个以当前字符为结尾时的子串长度
    const dp = []
    let max = 1
    dp[0] = 1
    idxMap.set(str[0], 0)
    for (let i = 1; i < str.length; i++) {
        const preIdx = idxMap.get(str[i])
        // 当前字符上一次出现的位置
        if (preIdx !== undefined) {
            dp[i] = Math.min(dp[i - 1] + 1, i - preIdx)
        } else {
            dp[i] = dp[i - 1] + 1
        }
        max = Math.max(max, dp[i])
        idxMap.set(str[i], i)
    }
    return max
};