LeetCode第一周

174 阅读8分钟

两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9

所以返回 [0, 1]

  • 难易程度:简单

解题思路:

  1. 暴力解法
  • 两层for循环,数组的第一个值与第二,三,四...个值相加判断是否等于目标值,由于每个值只能使用一次,所以内存for起始值为 i + 1
var twoSum = function (nums: number[], target: number): number[] {
    if (nums.length < 2) return []
    
    if (nums.length === 2){
        if (nums[0] + nums[1] === target) return [0, 1]
        return []
    }

    for (var i = 0; i < nums.length; i++) {
        for (var j = i + 1; j < nums.length; j++) {
            if (nums[i] + nums[j] === target) {
                return [i, j]
            }
        }
    }
    return []
};

 console.log(twoSum([2, 7], 9))
  1. 哈希表解法
  • 利用对象存储已经循环过的值,比较目标值和当前正在循环的值的差值,如果差值正好在对象中,则找到了两个值
var towSumHash = function (nums: number[], target: number): number[] {
    if (nums.length < 2) return []

    if (nums.length === 2) {
        if (nums[0] + nums[1] === target) return [0, 1]
        return []
    }

    var map = {} // key: 数字  value:index
    var loop = 0 // 循环次数
    var dis = 0 // 差值
    var len = nums.length

    while (loop < len) {
        dis = target - nums[loop]

        if (map[dis] != undefined) {
            return [map[dis], loop]
        }
        map[nums[loop]] = loop
        loop++
    }

    return []
}

console.log(towSumHash([2, 7, 122, 123, 23], 129))

两数相加

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆 序 的方式存储的,并且它们的每个节点只能存储 一位 数字。

如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。

您可以假设除了数字 0 之外,这两个数都不会以 0 开头。

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)

输出:7 -> 0 -> 8

原因:342 + 465 = 807

  • 难易程度: 中等

解题思路

  1. 平常思维,按序取出链表中的值,得出相加的结果,再利用结果倒序生成新的链表
class LinkedNode {
    value: number
    next: LinkedNode
    constructor(value: number) {
        this.next = null
        this.value = value
    }
}

const a = new LinkedNode(2)
const b = new LinkedNode(4)
const c = new LinkedNode(3)

const d = new LinkedNode(5)
const e = new LinkedNode(6)
const f = new LinkedNode(4)

a.next = b
b.next = c

d.next = e
e.next = f

function getNode(linkedList: LinkedNode, values: number[]) {
    values.push(linkedList.value)
    if (linkedList.next !== null) {
        getNode(linkedList.next, values)
    }
}
// 按序取出链表中的值的数组
function getNodeArr(linkedList: LinkedNode) {
    let values: number[] = []
    getNode(linkedList, values)
    return values
}

const addTwoNumbers = function (l1: LinkedNode, l2: LinkedNode) {
    let value1 = getNodeArr(l1).reverse().join('')
    let value2 = getNodeArr(l2).reverse().join('')

    let res = (+value1) + (+value2) + ''

    // 存储结果的每个节点
    const nodeArr: LinkedNode[] = []
    res.split('').reverse().forEach(r => {
        nodeArr.push(new LinkedNode(+r))
    })

    for (let i = 0, len = nodeArr.length; i < len - 1; i++) {
        // 构建结果链表
        nodeArr[i].next = nodeArr[i + 1]
    }

    return nodeArr[0]
};

console.log(addTwoNumbers(a, d)) // 7 -> 0 -> 8
  1. 两个链表相加,因为是倒序的,则两数相加大于10,向后进位,低位相加。

2 -> 4 -> 3

5 -> 6 -> 4 +

----------------------

7 -> 0 -> 8

因为6+4大于等于10, 所以需要向后进位, 3 + 4 + 1 = 8

function addTwoNumbers2(l1: LinkedNode, l2: LinkedNode): LinkedNode {
    let node = new LinkedNode(0);
    let temp = node;// temp节点
    let add = 0;// 是否进一
    let sum = 0;// 新链表当前未取余的值 = 链表1值 + 链表2值 + add;

    // 遍历, 以最长链表为结束点
    while (l1 || l2) {
        sum = (l1 ? l1.value : 0) + (l2 ? l2.value : 0) + add;
        temp.next = new LinkedNode(sum % 10);// 取余则为新链表的值
        temp = temp.next;
        add = sum >= 10 ? 1 : 0; // 相加大于10,则需向后进位(+1)
        l1 && (l1 = l1.next); // 改变指向,继续循环
        l2 && (l2 = l2.next);
    }
    add && (temp.next = new LinkedNode(add));
    return node.next;
}

最长无重复子串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例:

输入: "abcabcbb"

输出: 3

解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

难易度: 中等

  1. 无重复子串 = 无重复字符开始 至 当前遍历字符串下标

当出现重复子串时,更新无重复子串开始位置

abcabca出现第一个重复子串a之前, 无重复子串为abc,长度为2 - 0 + 1 (遍历点 - 无重复子串开始位置 + 1)

var lengthOfLongestSubstring = function(s: string) {
    if (!s) return 0
    if (s.length === 1) return 1

    let map: Map<string, number> = new Map() // 使用map存储已经遍历过的字符,k:字符  v:index
    let max: number = 0
    let len = s.length
    let i = 0 // 无重复子串的开始位置

    // 从无重复子串位置 到 遍历位置就为最长无重复子串 i->j 为无重复子串
    for(let j = 0; j < len; j++) {
        if(map.has(s[j])) {
            // 如果这个字符被遍历过,更新无重复子串的开始位置为 这个字符出现的最大位置
            i = Math.max(map.get(s[j]) + 1, i)
        }
        // 最长无重复子串就为 当前遍历下标 - 无重复子串开始位置 + 1(长度需要+1)
        max = Math.max(max, j - i + 1)
        // 记录当前字符的位置
        map.set(s[j], j)
    }
    return max
}

console.log(lengthOfLongestSubstring('abcabcaa'))
  1. 无重复子串指针,当出现了重复子串,移动无重复子串指针,无重复子串为 指针到当前遍历位置的子串
var lengthOfLongestSubstring2 = function (s: string) {
    if (!s) return 0
    if (s.length === 1) return 1

    let max = 0 // 最大长度
    let temp = "" // 当前无重复字串
    let start = 0 // 当前无重复字串指针
    let loop = 0 // 循环次数
    let index: number // 当前字符所在temp中的位置
    let len = s.length

    //假如当前max大于当前未遍历的剩余字符时,不需要往后遍历,不存在更长的不重复子串
    //abccba 当运行到第二个c时,max = start = 3 不需要继续循环
    while (loop < len && max < len - start) {
        index = temp.indexOf(s[loop])
        if (index >= 0) {
            start += index + 1 // 出现了重复字符,移动指针,舍弃第一个重复字符位置之前的字符
        }
        temp = s.slice(start, loop + 1)
        max = Math.max(temp.length, max)
        loop++
    }
    return max
};

整数反转

给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。

示例 1: 输入: 123 输出: 321

示例 2: 输入: -123 输出: -321

示例 3: 输入: 120 输出: 21

假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−231, 231 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。

难易度:简单

解题思路

  1. 暴力解法

    转换为字符串,反转字符串即可

var reverse = function(x: number): number {
    let maxNum = Math.pow(2, 31)
    if (x === 0 || x < -maxNum || x > maxNum ) return 0

    let num = +(Math.abs(x) + '').split('').reverse().join('')
    if (x < 0) return -num
    if (x > 0) return num
};
  1. 取余,取模

    每次取原数字的最后一位,作为新数字的开始位,操作完成后截取掉原数字的最后一位,完成反转

    214 取模 4 -> result: 4 origin: 21

    21 取模 1 -> result: 41 origin: 2

    2 取模 2 -> result: 412 origin:

var reverse2 = function (x: number): number {
    let maxNum = Math.pow(2, 31)
    if (x === 0 || x < -maxNum || x > maxNum ) return 0

    let num = Math.abs(x) // 将所有数变成正数,操作过程中不关心正负
    let result = 0

    while(num > 0) {
        // 每次都操作最后一位数,所以每次操作数都是10的倍数
        result = result * 10 + num % 10 
        num = Math.floor(num / 10)
    }
    if (x < 0) return -result
    if (x > 0) return result
}

罗马数字转整数

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

字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000

例如, 罗马数字 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 的范围内

  • 示例:

    输入: "III"

    输出: 3

难易度:中等

解题思路:

  1. 根据特殊规则,如果前一位罗马数 / 后一位罗马数 = 1/5 或者 1/10,则,这两位罗马数应组合成一位阿拉伯数字,值为后一位罗马数 - 前一位罗马数
var romanToInt = function (s: string): number {
    if (!s) return 0
    let obj = {
        I: 1,
        V: 5,
        X: 10,
        L: 50,
        C: 100,
        D: 500,
        M: 1000
    }
    let len = s.length
    let loop = 0
    let res = 0

    while (loop < len) {
        let temp = obj[s[loop]]
        let temp2 = obj[s[loop + 1]]

        if (temp2 / temp === 5 || temp2 / temp === 10) {
            res += (temp2 - temp)
            loop +=2 // 消耗了两位罗马数,所以循环次数+2
        } else {
            res += temp
            loop++ // 这里只需要消耗一位罗马数,所以+1
        }
    }
    return res
};
  1. 根据特殊情况,前一位数如果比后一位数小,则这两位数应组合在一起
var romanToInt2 = function (s: string): number {
    if (!s) return 0
    let obj = {
        I: 1,
        V: 5,
        X: 10,
        L: 50,
        C: 100,
        D: 500,
        M: 1000
    }
    let len = s.length, sum = 0, loop = 0, num = 0, now = 0;
    while(loop < len) {
        now = obj[s[loop]] // 用now代表当前罗马数对应的阿拉伯数
        if(num < now) { 
            sum -= num // 如果上一位数小于当前数,那么肯定是这两位罗马数组合,值为两位数相减
        } else {
            sum += num
        }
        num = now // 用num缓存上一位罗马数对应的阿拉伯数
        loop++
    }
    return sum += num
};