50

50

Q1.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
/* *
 * 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。
 * 你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
 * 你可以按任意顺序返回答案。
 */


function twoSum(nums: number[], target: number): number[] {
    let hashTable: Map<number, number> = new Map();

    for (let i = 0; i < nums.length; i++) {
        let deserved = target - nums[i];
        if (hashTable.get(deserved)) {
            return [hashTable.get(deserved), i];
        }
        hashTable.set(nums[i], i);
    }
    
    return [-1, -1];
}

Q3.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
 * 给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串 的长度。
 * 
示例 1:
输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3

示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1

示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
 
提示:
0 <= s.length <= 5 * 104
s 由英文字母、数字、符号和空格组成
 * @param s 
 * @returns 
 */

function lengthOfLongestSubstring(s: string): number {
    const set = new Set();
    const len = s.length;

    let rk = -1,
        ans = 0;

    for (let i = 0; i < len; i++) {
        if (i != 0) set.delete(s.charAt(i - 1));
        while (rk + 1 < len && !set.has(s.charAt(rk + 1))) {
            set.add(s.charAt(rk + 1));
            rk++;
        }
        ans = Math.max(ans, rk - i + 1);
    }
    return ans;
}

Q6.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
 * 将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 "PAYPALISHIRING" 行数为 3 时,排列如下:
P   A   H   N
A P L S I I G
Y   I   R
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"PAHNAPLSIIGYIR"

请你实现这个将字符串进行指定行数变换的函数:

string convert(string s, int numRows);
 * @param s 
 * @param numRows 
 */

function convert(s: string, numRows: number): string {
    if (numRows === 1 || numRows >= s.length) {
        // 一行或者超出行数
        return s;
    }
    const t = numRows * 2 - 2;
    const ans = [];
    for (let i = 0; i < numRows; i++) {
        // 枚举矩阵的行
        for (let j = 0; j < s.length - i; j += t) {
            // 枚举每个周期的起始下标
            ans.push(s[j + i]); // 当前周期的第一个字符
            if (0 < i && i < numRows - 1 && j + t - i < s.length) {
                ans.push(s[j + t - i]); // 当前周期的第二个字符
            }
        }
    }
    return ans.join("");
}

Q12.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/**
 * 七个不同的符号代表罗马数字,其值如下:
符号	
I	1
V	5
X	10
L	50
C	100
D	500
M	1000
罗马数字是通过添加从最高到最低的小数位值的转换而形成的。将小数位值转换为罗马数字有以下规则:
如果该值不是以 4  9 开头,请选择可以从输入中减去的最大值的符号,
将该符号附加到结果,减去其值,然后将其余部分转换为罗马数字。
如果该值以 4  9 开头,使用 减法形式,表示从以下符号中减去一个符号,
例如 4  5 (V)  1 (I): IV 9  10 (X)  1 (I)IX
仅使用以下减法形式:4 (IV)9 (IX)40 (XL)90 (XC)400 (CD)  900 (CM)
只有 10 的次方(I, X, C, M)最多可以连续附加 3 次以代表 10 的倍数。
你不能多次附加 5 (V)50 (L)  500 (D)。如果需要将符号附加4次,请使用 减法形式。
给定一个整数,将其转换为罗马数字。

示例 1
输入:num = 3749
输出: "MMMDCCXLIX"
解释:
3000 = MMM 由于 1000 (M) + 1000 (M) + 1000 (M)
 700 = DCC 由于 500 (D) + 100 (C) + 100 (C)
  40 = XL 由于 50 (L)  10 (X)
   9 = IX 由于 10 (X)  1 (I)
注意:49 不是 50 (L)  1 (I) 因为转换是基于小数位

示例 2
输入:num = 58
输出:"LVIII"
解释:
50 = L
 8 = VIII

示例 3
输入:num = 1994
输出:"MCMXCIV"
解释:
1000 = M
 900 = CM
  90 = XC
   4 = IV
提示:
1 <= num <= 3999
 */

function intToRoman(num: number): string {
    const valueSymbols: Array<[number, string]> = [
        [1000, "M"],
        [900, "CM"],
        [500, "D"],
        [400, "CD"],
        [100, "C"],
        [90, "XC"],
        [50, "L"],
        [40, "XL"],
        [10, "X"],
        [9, "IX"],
        [5, "V"],
        [4, "IV"],
        [1, "I"],
    ];
    const roman = [];

    for (const [value, symbol] of valueSymbols) {
        while (num >= value) {
            num -= value;
            roman.push(symbol);
        }
        if (num == 0) break;
    }

    return roman.join("");
}

Q14.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
 * 编写一个函数来查找字符串数组中的最长公共前缀。
 * 如果不存在公共前缀,返回空字符串 ""。
 * @param strs
 */

function longestCommonPrefix(strs: string[]): string {
    if (strs == null || strs.length == 0) return "";

    let prefix = strs[0];
    for (let i = 1; i < strs.length; i++) {
        prefix = lcp(prefix, strs[i]);
        if (prefix.length == 0) break;
    }

    return prefix;
}

function lcp(str1: string, str2: string): string {
    let length = Math.min(str1.length, str2.length);
    let index = 0;

    while (index < length && str1[index] == str2[index]) {
        index++;
    }

    return str1.substring(0, index);
}

Q15.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
 * 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]]
 * 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。
 * 请你返回所有和为 0 且不重复的三元组。
 * 注意:答案中不可以包含重复的三元组。
 * @param nums
 * @returns
 */

function threeSum(nums: number[]): number[][] {
    let len: number = nums.length;
    nums.sort((a, b) => a - b);
    let target: number, third: number;
    let arr: Array<Array<number>> = [];
    for (let first: number = 0; first < len; first++) {
        if (first > 0 && nums[first] == nums[first - 1]) continue; //与上一个不同
        third = len - 1; //从尾部开始
        target = -nums[first]; //目标和

        for (let second = first + 1; second < len; second++) {
            if (second > first + 1 && nums[second] == nums[second - 1])
                continue;
            while (second < third && nums[second] + nums[third] > target)
                third--;
            if (second == third) break;

            if (nums[second] + nums[third] == target) {
                arr.push([nums[first], nums[second], nums[third]]);
            }
        }
    }
    return arr;
}

Q19.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
 * 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
 * @param {*} val
 * @param {*} next
 */
function ListNode(val, next) {
    this.val = val === undefined ? 0 : val;
    this.next = next === undefined ? null : next;
}

var removeNthFromEnd = function (head, n) {
    let dummy = new ListNode(0, head);
    let first = head;
    let second = dummy;

    for (let i = 0; i < n; i++) first = first.next;
    while (first != null) {
        first = first.next;
        second = second.next;
    }
    second.next = second.next.next;
    return dummy.next;
};

Q21.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
 * 将两个升序链表合并为一个新的 升序 链表并返回。
 * 新链表是通过拼接给定的两个链表的所有节点组成的。
 */

class ListNode {
    val: number;
    next: ListNode | null;
    constructor(val?: number, next?: ListNode | null) {
        this.val = val === undefined ? 0 : val;
        this.next = next === undefined ? null : next;
    }
}

function mergeTwoLists(
    list1: ListNode | null,
    list2: ListNode | null,
): ListNode | null {
    const prehead = new ListNode(-1);

    let prev = prehead;

    while (list1 != null && list2 != null) {
        if (list1.val <= list2.val) {
            prev.next = list1;
            list1 = list1.next;
        } else {
            prev.next = list2;
            list2 = list2.next;
        }
        prev = prev.next;
    }

    prev.next = list1 === null ? list2 : list1;

    return prehead.next;
}

Q26.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
 * 给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,
 * 使每个元素 只出现一次 ,返回删除后数组的新长度。
 * 元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:

更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
返回 k 。
判题标准:

系统会用下面的代码来测试你的题解:

int[] nums = [...]; // 输入数组
int[] expectedNums = [...]; // 长度正确的期望答案

int k = removeDuplicates(nums); // 调用

assert k == expectedNums.length;
for (int i = 0; i < k; i++) {
    assert nums[i] == expectedNums[i];
}
如果所有断言都通过,那么您的题解将被 通过。

 

示例 1:

输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
示例 2:

输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。
 

提示:

1 <= nums.length <= 3 * 104
-104 <= nums[i] <= 104
nums 已按 非严格递增 排列
 */

function removeDuplicates(nums: number[]): number {
    if (nums.length == 0) return 0;
    let slow = 1,
        fast = 1;
    while (fast < nums.length) {
        if (nums[fast] != nums[fast - 1]) {
            nums[slow] = nums[fast];
            slow++;
        }
        fast++;
    }
    return slow;
}

Q27.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
 * 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。
 * 元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。

假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:

更改 nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。
返回 k。
用户评测:

评测机将使用以下代码测试您的解决方案:

int[] nums = [...]; // 输入数组
int val = ...; // 要移除的值
int[] expectedNums = [...]; // 长度正确的预期答案。
                            // 它以不等于 val 的值排序。

int k = removeElement(nums, val); // 调用你的实现

assert k == expectedNums.length;
sort(nums, 0, k); // 排序 nums 的前 k 个元素
for (int i = 0; i < actualLength; i++) {
    assert nums[i] == expectedNums[i];
}
如果所有的断言都通过,你的解决方案将会 通过。

 

示例 1:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2,_,_]
解释:你的函数函数应该返回 k = 2, 并且 nums 中的前两个元素均为 2。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。
示例 2:

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3,_,_,_]
解释:你的函数应该返回 k = 5,并且 nums 中的前五个元素为 0,0,1,3,4。
注意这五个元素可以任意顺序返回。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。
 

提示:

0 <= nums.length <= 100
0 <= nums[i] <= 50
0 <= val <= 100
 */

function removeElement(nums: number[], val: number): number {
    let left = 0,
        right = nums.length - 1;
    while (left <= right) {
        // left指的为val,找right有效的值拿过来
        if (nums[left] == val) {
            nums[left] = nums[right];
            right--;
        } else {
            // left指的不是val,前left个值为正确的
            left++;
        }
    }
    return left;
}

Q36.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
 *请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
 
注意:
一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
空白格用 '.' 表示。
 * @param board
 * @returns
 */

function isValidSudoku(board: string[][]): boolean {
    const rows = new Array(9).fill(0).map(() => new Array(9).fill(0));
    const columns = new Array(9).fill(0).map(() => new Array(9).fill(0));
    const subboxes = new Array(3)
        .fill(0)
        .map(() => new Array(3).fill(0).map(() => new Array(9).fill(0)));

    for (let i = 0; i < 9; i++) {
        for (let j = 0; j < 9; j++) {
            const c = board[i][j];
            if (c !== ".") {
                const index = c.charCodeAt(0) - "0".charCodeAt(0) - 1;
                rows[i][index]++;
                columns[j][index]++;
                subboxes[Math.floor(i / 3)][Math.floor(j / 3)][index]++;
                if (
                    rows[i][index] > 1 ||
                    columns[j][index] > 1 ||
                    subboxes[Math.floor(i / 3)][Math.floor(j / 3)][index] > 1
                ) {
                    return false;
                }
            }
        }
    }
    return true;
}

Q42.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/**
 * 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 
示例 2
输入:height = [4,2,0,3,2,5]
输出:9
 
提示:
n == height.length
1 <= n <= 2 * 104
0 <= height[i] <= 105
 */

// dp
function trap(height: number[]): number {
    const len = height.length;
    if (len == 0) return 0;

    const leftMax = new Array(len).fill(0);
    leftMax[0] = height[0];
    for (let i = 0; i < len; i++) {
        leftMax[i] = Math.max(leftMax[i - 1], height[i]);
    }

    const rightMax = new Array(len).fill(0);
    rightMax[len - 1] = height[len - 1];
    for (let i = len - 2; i >= 0; i--) {
        rightMax[i] = Math.max(rightMax[i + 1], height[i]);
    }

    let answer = 0;
    for (let i = 0; i < len; i++) {
        answer += Math.min(leftMax[i], rightMax[i]) - height[i];
    }
    return answer;
}

// stack
function trap(height: number[]): number {
    let answer = 0;
    let stack: Array<number> = [];

    for (let i = 0; i < height.length; i++) {
        // 判断stack顶元素是否小于当前元素stack里只保存递减元素
        while (stack.length && height[i] > height[stack[stack.length - 1]]) {
            const top = stack.pop();
            if (!stack.length) break;
            const left = stack[stack.length - 1];
            const currWidth = i - left - 1;
            const currHeight = Math.min(height[left], height[i]) - height[top];
            answer += currHeight * currWidth;
        }
        stack.push(i);
    }
}

// dp双指针
function trap(height: number[]): number {
    const len = height.length;
    if (len == 0) return 0;

    let left = 0,
        right = len - 1;
    let leftMax = height[left],
        rightMax = height[right];
    let answer = 0;

    while (left < right) {
        leftMax = Math.max(leftMax, height[left]);
        rightMax = Math.max(rightMax, height[right]);
        if (height[left] < height[right]) {
            // 区间左右壁为leftmax height[right]
            answer += leftMax - height[left];
            left++;
        } else {
            answer += rightMax - height[right];
            right--;
        }
    }

    return answer;
}

Q45.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
 * 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。
每个元素 nums[i] 表示从索引 i 向后跳转的最大长度。
换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:
0 <= j <= nums[i] 
i + j < n
返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]。

示例 1:
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
     从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

示例 2:
输入: nums = [2,3,0,1,4]
输出: 2
 
提示:
1 <= nums.length <= 104
0 <= nums[i] <= 1000
题目保证可以到达 nums[n-1]
 * @param nums 
 * @returns 
 */

function jump(nums: number[]): number {
    let end: number = 0, // 当前step的末尾
        maxPosition: number = 0,
        step: number = 0;

    for (let i: number = 0; i < nums.length - 1; i++) {
        maxPosition = Math.max(maxPosition, i + nums[i]);
        if (i == end) {
            end = maxPosition; // 将下一个step的maxPosition交给end
            step++;
        }
    }
    return step;
}

Q46.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
 * 给定一个不含重复数字的数组 nums ,
 * 返回其 所有可能的全排列 。
 * 你可以 按任意顺序 返回答案。
 * 示例 1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

示例 2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:
输入:nums = [1]
输出:[[1]]
 * @param nums
 * @returns
 */

function permute(nums: number[]): number[][] {
    let res: number[][] = [];
    let output = Array.from(nums);
    let len = nums.length;

    backtrack(len, output, res, 0);
    return res;
}

function backtrack(
    len: number,
    output: number[],
    res: number[][],
    first: number,
) {
    if (first === len) {
        res.push(Array.from(output));
        return;
    }
    for (let i = first; i < len; i++) {
        [output[first], output[i]] = [output[i], output[first]];
        backtrack(len, output, res, first + 1)[(output[first], output[i])] = [
            output[i],
            output[first],
        ];
    }
}
Licensed under CC BY-NC-SA 4.0
最后更新于 Mar 30, 2025 10:07 UTC