Leetcode: 0001-0010题速览

Leetcode: 0001-0010题速览

本文材料来自于LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解

遵从开源协议为知识共享 版权归属-相同方式共享 4.0 国际 公共许可证

研一在读备战就业,制此集合便于空闲时间浏览,有任何疑惑问题欢迎讨论,共同进步

1. 两数之和

题目描述

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。

你可以按任意顺序返回答案。

 

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

 

提示:

  • 2 <= nums.length <= 104
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109
  • 只会存在一个有效答案

 

进阶:你可以想出一个时间复杂度小于 O(n2) 的算法吗?

难度:简单

标签:数组,哈希表

解法

方法一:哈希表

我们可以使用一个哈希表 d \textit{d} d 来存储每个元素及其对应的索引。

遍历数组 nums \textit{nums} nums,对于当前元素 nums [ i ] \textit{nums}[i] nums[i],我们首先判断 target − nums [ i ] \textit{target} - \textit{nums}[i] targetnums[i] 是否在哈希表 d \textit{d} d 中,如果在 d \textit{d} d 中,说明 target \textit{target} target 值已经找到,返回 target − nums [ i ] \textit{target} - \textit{nums}[i] targetnums[i] 的索引和 i i i 即可。

时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n),其中 n n n 为数组 nums \textit{nums} nums 的长度。

Python3
class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        d = {}
        for i, x in enumerate(nums):
            y = target - x
            if y in d:
                return [d[y], i]
            d[x] = i
Java
class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> d = new HashMap<>();
        for (int i = 0;; ++i) {
            int x = nums[i];
            int y = target - x;
            if (d.containsKey(y)) {
                return new int[] {d.get(y), i};
            }
            d.put(x, i);
        }
    }
}
C++
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> d;
        for (int i = 0;; ++i) {
            int x = nums[i];
            int y = target - x;
            if (d.contains(y)) {
                return {d[y], i};
            }
            d[x] = i;
        }
    }
};

2. 两数相加

题目描述

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

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

 

示例 1:

链表加法

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.

示例 2:

输入:l1 = [0], l2 = [0]
输出:[0]

示例 3:

输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]

 

提示:

  • 每个链表中的节点数在范围 [1, 100]
  • 0 <= Node.val <= 9
  • 题目数据保证列表表示的数字不含前导零

难度:中等

标签:递归,链表,数学

解法

方法一:模拟

我们同时遍历两个链表 l 1 l_1 l1 l 2 l_2 l2,并使用变量 c a r r y carry carry 表示当前是否有进位。

每次遍历时,我们取出对应链表的当前位,计算它们与进位 c a r r y carry carry 的和,然后更新进位的值,最后将当前位的值加入答案链表。如果两个链表都遍历完了,并且进位为 0 0 0 时,遍历结束。

最后我们返回答案链表的头节点即可。

时间复杂度 O ( max ⁡ ( m , n ) ) O(\max(m, n)) O(max(m,n)),其中 m m m n n n 分别为两个链表的长度。我们需要遍历两个链表的全部位置,而处理每个位置只需要 O ( 1 ) O(1) O(1) 的时间。忽略答案的空间消耗,空间复杂度 O ( 1 ) O(1) O(1)

Python3
## Definition for singly-linked list.
## class ListNode:
##     def __init__(self, val=0, next=None):
##         self.val = val
##         self.next = next
class Solution:
    def addTwoNumbers(
        self, l1: Optional[ListNode], l2: Optional[ListNode]
    ) -> Optional[ListNode]:
        dummy = ListNode()
        carry, curr = 0, dummy
        while l1 or l2 or carry:
            s = (l1.val if l1 else 0) + (l2.val if l2 else 0) + carry
            carry, val = divmod(s, 10)
            curr.next = ListNode(val)
            curr = curr.next
            l1 = l1.next if l1 else None
            l2 = l2.next if l2 else None
        return dummy.next
Java
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(0);
        int carry = 0;
        ListNode cur = dummy;
        while (l1 != null || l2 != null || carry != 0) {
            int s = (l1 == null ? 0 : l1.val) + (l2 == null ? 0 : l2.val) + carry;
            carry = s / 10;
            cur.next = new ListNode(s % 10);
            cur = cur.next;
            l1 = l1 == null ? null : l1.next;
            l2 = l2 == null ? null : l2.next;
        }
        return dummy.next;
    }
}
C++
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* dummy = new ListNode();
        int carry = 0;
        ListNode* cur = dummy;
        while (l1 || l2 || carry) {
            int s = (l1 ? l1->val : 0) + (l2 ? l2->val : 0) + carry;
            carry = s / 10;
            cur->next = new ListNode(s % 10);
            cur = cur->next;
            l1 = l1 ? l1->next : nullptr;
            l2 = l2 ? l2->next : nullptr;
        }
        return dummy->next;
    }
};

3. 无重复字符的最长子串

题目描述

给定一个字符串 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 由英文字母、数字、符号和空格组成

难度:中等

标签:哈希表,字符串,滑动窗口

解法

方法一:双指针 + 哈希表

定义一个哈希表记录当前窗口内出现的字符,记 i i i j j j 分别表示不重复子串的开始位置和结束位置,无重复字符子串的最大长度记为 ans

遍历字符串 s 的每个字符 s [ j ] s[j] s[j],我们记为 c c c。若 s [ i . . j − 1 ] s[i..j-1] s[i..j1] 窗口内存在 c c c,则 i i i 循环向右移动,更新哈希表,直至 s [ i . . j − 1 ] s[i..j-1] s[i..j1] 窗口不存在 c,循环结束。将 c 加入哈希表中,此时 s [ i . . j ] s[i..j] s[i..j] 窗口内不含重复元素,更新 ans 的最大值。

最后返回 ans 即可。

时间复杂度 O ( n ) O(n) O(n),其中 n n n 表示字符串 s 的长度。

双指针算法模板:

for (int i = 0, j = 0; i < n; ++i) {
    while (j < i && check(j, i)) {
        ++j;
    }
    // 具体问题的逻辑
}
Python3
class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        ss = set()
        ans = i = 0
        for j, c in enumerate(s):
            while c in ss:
                ss.remove(s[i])
                i += 1
            ss.add(c)
            ans = max(ans, j - i + 1)
        return ans
Java
class Solution {
    public int lengthOfLongestSubstring(String s) {
        boolean[] ss = new boolean[128];
        int ans = 0;
        for (int i = 0, j = 0; j < s.length(); ++j) {
            char c = s.charAt(j);
            while (ss[c]) {
                ss[s.charAt(i++)] = false;
            }
            ss[c] = true;
            ans = Math.max(ans, j - i + 1);
        }
        return ans;
    }
}
C++
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        bool ss[128]{};
        int ans = 0;
        for (int i = 0, j = 0; j < s.size(); ++j) {
            while (ss[s[j]]) {
                ss[s[i++]] = false;
            }
            ss[s[j]] = true;
            ans = max(ans, j - i + 1);
        }
        return ans;
    }
};

4. 寻找两个正序数组的中位数

题目描述

给定两个大小分别为 mn 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数

算法的时间复杂度应该为 O(log (m+n))

 

示例 1:

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2

示例 2:

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

 

 

提示:

  • nums1.length == m
  • nums2.length == n
  • 0 <= m <= 1000
  • 0 <= n <= 1000
  • 1 <= m + n <= 2000
  • -106 <= nums1[i], nums2[i] <= 106

难度:困难

标签:数组,二分查找,分治

解法

方法一:分治

题目要求算法的时间复杂度为 O ( log ⁡ ( m + n ) ) O(\log (m + n)) O(log(m+n)),因此不能直接遍历两个数组,而是需要使用二分查找的方法。

如果 m + n m + n m+n 是奇数,那么中位数就是第 ⌊ m + n + 1 2 ⌋ \left\lfloor\frac{m + n + 1}{2}\right\rfloor 2m+n+1 个数;如果 m + n m + n m+n 是偶数,那么中位数就是第 ⌊ m + n + 1 2 ⌋ \left\lfloor\frac{m + n + 1}{2}\right\rfloor 2m+n+1 和第 ⌊ m + n + 2 2 ⌋ \left\lfloor\frac{m + n + 2}{2}\right\rfloor 2m+n+2 个数的平均数。实际上,我们可以统一为求第 ⌊ m + n + 1 2 ⌋ \left\lfloor\frac{m + n + 1}{2}\right\rfloor 2m+n+1 个数和第 ⌊ m + n + 2 2 ⌋ \left\lfloor\frac{m + n + 2}{2}\right\rfloor 2m+n+2 个数的平均数。

因此,我们可以设计一个函数 f ( i , j , k ) f(i, j, k) f(i,j,k),表示在数组 n u m s 1 nums1 nums1 的区间 [ i , m ) [i, m) [i,m) 和数组 n u m s 2 nums2 nums2 的区间 [ j , n ) [j, n) [j,n) 中,求第 k k k 小的数。那么中位数就是 f ( 0 , 0 , ⌊ m + n + 1 2 ⌋ ) f(0, 0, \left\lfloor\frac{m + n + 1}{2}\right\rfloor) f(0,0,2m+n+1) f ( 0 , 0 , ⌊ m + n + 2 2 ⌋ ) f(0, 0, \left\lfloor\frac{m + n + 2}{2}\right\rfloor) f(0,0,2m+n+2) 的平均数。

函数 f ( i , j , k ) f(i, j, k) f(i,j,k) 的实现思路如下:

  • 如果 i ≥ m i \geq m im,说明数组 n u m s 1 nums1 nums1 的区间 [ i , m ) [i, m) [i,m) 为空,因此直接返回 n u m s 2 [ j + k − 1 ] nums2[j + k - 1] nums2[j+k1]
  • 如果 j ≥ n j \geq n jn,说明数组 n u m s 2 nums2 nums2 的区间 [ j , n ) [j, n) [j,n) 为空,因此直接返回 n u m s 1 [ i + k − 1 ] nums1[i + k - 1] nums1[i+k1]
  • 如果 k = 1 k = 1 k=1,说明要找第一个数,因此只需要返回 n u m s 1 [ i ] nums1[i] nums1[i] n u m s 2 [ j ] nums2[j] nums2[j] 中的最小值;
  • 否则,我们分别在两个数组中查找第 ⌊ k 2 ⌋ \left\lfloor\frac{k}{2}\right\rfloor 2k 个数,设为 x x x y y y。(注意,如果某个数组不存在第 ⌊ k 2 ⌋ \left\lfloor\frac{k}{2}\right\rfloor 2k 个数,那么我们将第 ⌊ k 2 ⌋ \left\lfloor\frac{k}{2}\right\rfloor 2k 个数视为 + ∞ +\infty +。)比较 x x x y y y 的大小:
    • 如果 x ≤ y x \leq y xy,则说明数组 n u m s 1 nums1 nums1 的第 ⌊ k 2 ⌋ \left\lfloor\frac{k}{2}\right\rfloor 2k 个数不可能是第 k k k 小的数,因此我们可以排除数组 n u m s 1 nums1 nums1 的区间 [ i , i + ⌊ k 2 ⌋ ) [i, i + \left\lfloor\frac{k}{2}\right\rfloor) [i,i+2k),递归调用 f ( i + ⌊ k 2 ⌋ , j , k − ⌊ k 2 ⌋ ) f(i + \left\lfloor\frac{k}{2}\right\rfloor, j, k - \left\lfloor\frac{k}{2}\right\rfloor) f(i+2k,j,k2k)
    • 如果 x > y x > y x>y,则说明数组 n u m s 2 nums2 nums2 的第 ⌊ k 2 ⌋ \left\lfloor\frac{k}{2}\right\rfloor 2k 个数不可能是第 k k k 小的数,因此我们可以排除数组 n u m s 2 nums2 nums2 的区间 [ j , j + ⌊ k 2 ⌋ ) [j, j + \left\lfloor\frac{k}{2}\right\rfloor) [j,j+2k),递归调用 f ( i , j + ⌊ k 2 ⌋ , k − ⌊ k 2 ⌋ ) f(i, j + \left\lfloor\frac{k}{2}\right\rfloor, k - \left\lfloor\frac{k}{2}\right\rfloor) f(i,j+2k,k2k)

时间复杂度 O ( log ⁡ ( m + n ) ) O(\log(m + n)) O(log(m+n)),空间复杂度 O ( log ⁡ ( m + n ) ) O(\log(m + n)) O(log(m+n))。其中 m m m n n n 分别是数组 n u m s 1 nums1 nums1 n u m s 2 nums2 nums2 的长度。

Python3
class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        def f(i: int, j: int, k: int) -> int:
            if i >= m:
                return nums2[j + k - 1]
            if j >= n:
                return nums1[i + k - 1]
            if k == 1:
                return min(nums1[i], nums2[j])
            p = k // 2
            x = nums1[i + p - 1] if i + p - 1 < m else inf
            y = nums2[j + p - 1] if j + p - 1 < n else inf
            return f(i + p, j, k - p) if x < y else f(i, j + p, k - p)

        m, n = len(nums1), len(nums2)
        a = f(0, 0, (m + n + 1) // 2)
        b = f(0, 0, (m + n + 2) // 2)
        return (a + b) / 2
Java
class Solution {
    private int m;
    private int n;
    private int[] nums1;
    private int[] nums2;

    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        m = nums1.length;
        n = nums2.length;
        this.nums1 = nums1;
        this.nums2 = nums2;
        int a = f(0, 0, (m + n + 1) / 2);
        int b = f(0, 0, (m + n + 2) / 2);
        return (a + b) / 2.0;
    }

    private int f(int i, int j, int k) {
        if (i >= m) {
            return nums2[j + k - 1];
        }
        if (j >= n) {
            return nums1[i + k - 1];
        }
        if (k == 1) {
            return Math.min(nums1[i], nums2[j]);
        }
        int p = k / 2;
        int x = i + p - 1 < m ? nums1[i + p - 1] : 1 << 30;
        int y = j + p - 1 < n ? nums2[j + p - 1] : 1 << 30;
        return x < y ? f(i + p, j, k - p) : f(i, j + p, k - p);
    }
}
C++
class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int m = nums1.size(), n = nums2.size();
        function<int(int, int, int)> f = [&](int i, int j, int k) {
            if (i >= m) {
                return nums2[j + k - 1];
            }
            if (j >= n) {
                return nums1[i + k - 1];
            }
            if (k == 1) {
                return min(nums1[i], nums2[j]);
            }
            int p = k / 2;
            int x = i + p - 1 < m ? nums1[i + p - 1] : 1 << 30;
            int y = j + p - 1 < n ? nums2[j + p - 1] : 1 << 30;
            return x < y ? f(i + p, j, k - p) : f(i, j + p, k - p);
        };
        int a = f(0, 0, (m + n + 1) / 2);
        int b = f(0, 0, (m + n + 2) / 2);
        return (a + b) / 2.0;
    }
};

5. 最长回文子串

题目描述

给你一个字符串 s,找到 s 中最长的 回文 子串。

 

示例 1:

输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。

示例 2:

输入:s = “cbbd”
输出:“bb”

 

提示:

  • 1 <= s.length <= 1000
  • s 仅由数字和英文字母组成

难度:中等

标签:双指针,字符串,动态规划

解法

方法一:动态规划

我们定义 f [ i ] [ j ] f[i][j] f[i][j] 表示字符串 s [ i . . j ] s[i..j] s[i..j] 是否为回文串,初始时 f [ i ] [ j ] = t r u e f[i][j] = true f[i][j]=true

接下来,我们定义变量 k k k m x mx mx,其中 k k k 表示最长回文串的起始位置, m x mx mx 表示最长回文串的长度。初始时 k = 0 k = 0 k=0, m x = 1 mx = 1 mx=1

考虑 f [ i ] [ j ] f[i][j] f[i][j],如果 s [ i ] = s [ j ] s[i] = s[j] s[i]=s[j],那么 f [ i ] [ j ] = f [ i + 1 ] [ j − 1 ] f[i][j] = f[i + 1][j - 1] f[i][j]=f[i+1][j1];否则 f [ i ] [ j ] = f a l s e f[i][j] = false f[i][j]=false。如果 f [ i ] [ j ] = t r u e f[i][j] = true f[i][j]=true 并且 m x < j − i + 1 mx \lt j - i + 1 mx<ji+1,那么我们更新 k = i k = i k=i, m x = j − i + 1 mx = j - i + 1 mx=ji+1

由于 f [ i ] [ j ] f[i][j] f[i][j] 依赖于 f [ i + 1 ] [ j − 1 ] f[i + 1][j - 1] f[i+1][j1],因此我们需要保证 i + 1 i + 1 i+1 j − 1 j - 1 j1 之前,因此我们需要从大到小地枚举 i i i,从小到大地枚举 j j j

时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( n 2 ) O(n^2) O(n2)。其中 n n n 是字符串 s s s 的长度。

Python3
class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)
        f = [[True] * n for _ in range(n)]
        k, mx = 0, 1
        for i in range(n - 2, -1, -1):
            for j in range(i + 1, n):
                f[i][j] = False
                if s[i] == s[j]:
                    f[i][j] = f[i + 1][j - 1]
                    if f[i][j] and mx < j - i + 1:
                        k, mx = i, j - i + 1
        return s[k : k + mx]
Java
class Solution {
    public String longestPalindrome(String s) {
        int n = s.length();
        boolean[][] f = new boolean[n][n];
        for (var g : f) {
            Arrays.fill(g, true);
        }
        int k = 0, mx = 1;
        for (int i = n - 2; i >= 0; --i) {
            for (int j = i + 1; j < n; ++j) {
                f[i][j] = false;
                if (s.charAt(i) == s.charAt(j)) {
                    f[i][j] = f[i + 1][j - 1];
                    if (f[i][j] && mx < j - i + 1) {
                        mx = j - i + 1;
                        k = i;
                    }
                }
            }
        }
        return s.substring(k, k + mx);
    }
}
C++
class Solution {
public:
    string longestPalindrome(string s) {
        int n = s.size();
        vector<vector<bool>> f(n, vector<bool>(n, true));
        int k = 0, mx = 1;
        for (int i = n - 2; ~i; --i) {
            for (int j = i + 1; j < n; ++j) {
                f[i][j] = false;
                if (s[i] == s[j]) {
                    f[i][j] = f[i + 1][j - 1];
                    if (f[i][j] && mx < j - i + 1) {
                        mx = j - i + 1;
                        k = i;
                    }
                }
            }
        }
        return s.substr(k, mx);
    }
};

6. Z 字形变换

题目描述

将一个给定字符串 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);

示例 1:

输入:s = “PAYPALISHIRING”, numRows = 3
输出:“PAHNAPLSIIGYIR”

示例 2:

输入:s = “PAYPALISHIRING”, numRows = 4
输出:“PINALSIGYAHRPI”
解释:
P I N
A L S I G
Y A H R
P I

示例 3:

输入:s = “A”, numRows = 1
输出:“A”

提示:

  • 1 <= s.length <= 1000
  • s 由英文字母(小写和大写)、',''.' 组成
  • 1 <= numRows <= 1000

难度:中等

标签:字符串

解法

方法一:模拟

我们用一个二维数组 g g g 来模拟 Z Z Z 字形排列的过程,其中 g [ i ] [ j ] g[i][j] g[i][j] 表示第 i i i 行第 j j j 列的字符。初始时 i = 0 i=0 i=0,另外我们定义一个方向变量 k k k,初始时 k = − 1 k=-1 k=1,表示向上走。

我们从左到右遍历字符串 s s s,每次遍历到一个字符 c c c,将其追加到 g [ i ] g[i] g[i] 中,如果此时 i = 0 i=0 i=0 或者 i = n u m R o w s − 1 i=numRows-1 i=numRows1,说明当前字符位于 Z Z Z 字形排列的拐点,我们将 k k k 的值反转,即 k = − k k=-k k=k。接下来,我们将 i i i 的值更新为 i + k i+k i+k,即向上或向下移动一行。继续遍历下一个字符,直到遍历完字符串 s s s,我们返回 g g g 中所有行拼接后的字符串即可。

时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)。其中 n n n 为字符串 s s s 的长度。

Python3
class Solution:
    def convert(self, s: str, numRows: int) -> str:
        if numRows == 1:
            return s
        g = [[] for _ in range(numRows)]
        i, k = 0, -1
        for c in s:
            g[i].append(c)
            if i == 0 or i == numRows - 1:
                k = -k
            i += k
        return ''.join(chain(*g))
Java
class Solution {
    public String convert(String s, int numRows) {
        if (numRows == 1) {
            return s;
        }
        StringBuilder[] g = new StringBuilder[numRows];
        Arrays.setAll(g, k -> new StringBuilder());
        int i = 0, k = -1;
        for (char c : s.toCharArray()) {
            g[i].append(c);
            if (i == 0 || i == numRows - 1) {
                k = -k;
            }
            i += k;
        }
        return String.join("", g);
    }
}
C++
class Solution {
public:
    string convert(string s, int numRows) {
        if (numRows == 1) {
            return s;
        }
        vector<string> g(numRows);
        int i = 0, k = -1;
        for (char c : s) {
            g[i] += c;
            if (i == 0 || i == numRows - 1) {
                k = -k;
            }
            i += k;
        }
        string ans;
        for (auto& t : g) {
            ans += t;
        }
        return ans;
    }
};

7. 整数反转

题目描述

给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。

如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。

假设环境不允许存储 64 位整数(有符号或无符号)。

示例 1:

输入:x = 123
输出:321

示例 2:

输入:x = -123
输出:-321

示例 3:

输入:x = 120
输出:21

示例 4:

输入:x = 0
输出:0

提示:

  • -231 <= x <= 231 - 1

难度:中等

标签:数学

解法

方法一:数学

我们不妨记 m i mi mi m x mx mx 分别为 − 2 31 -2^{31} 231 2 31 − 1 2^{31} - 1 2311,则 x x x 的反转结果 a n s ans ans 需要满足 m i ≤ a n s ≤ m x mi \le ans \le mx miansmx

我们可以通过不断地对 x x x 取余来获取 x x x 的最后一位数字 y y y,并将 y y y 添加到 a n s ans ans 的末尾。在添加 y y y 之前,我们需要判断 a n s ans ans 是否溢出。即判断 a n s × 10 + y ans \times 10 + y ans×10+y 是否在 [ m i , m x ] [mi, mx] [mi,mx] 的范围内。

x > 0 x \gt 0 x>0,那么需要满足 a n s × 10 + y ≤ m x ans \times 10 + y \leq mx ans×10+ymx,即 a n s × 10 + y ≤ ⌊ m x 10 ⌋ × 10 + 7 ans \times 10 + y \leq \left \lfloor \frac{mx}{10} \right \rfloor \times 10 + 7 ans×10+y10mx×10+7。整理得 ( a n s − ⌊ m x 10 ⌋ ) × 10 ≤ 7 − y (ans - \left \lfloor \frac{mx}{10} \right \rfloor) \times 10 \leq 7 - y (ans10mx)×107y

下面我们讨论上述不等式成立的条件:

  • a n s < ⌊ m x 10 ⌋ ans \lt \left \lfloor \frac{mx}{10} \right \rfloor ans<10mx 时,不等式显然成立;
  • a n s = ⌊ m x 10 ⌋ ans = \left \lfloor \frac{mx}{10} \right \rfloor ans=10mx 时,不等式成立的充要条件是 y ≤ 7 y \leq 7 y7。如果 a n s = ⌊ m x 10 ⌋ ans = \left \lfloor \frac{mx}{10} \right \rfloor ans=10mx 并且还能继续添加数字,说明此时数字是最高位,即此时 y y y 一定不超过 2 2 2,因此,不等式一定成立;
  • a n s > ⌊ m x 10 ⌋ ans \gt \left \lfloor \frac{mx}{10} \right \rfloor ans>10mx 时,不等式显然不成立。

综上,当 x > 0 x \gt 0 x>0 时,不等式成立的充要条件是 a n s ≤ ⌊ m x 10 ⌋ ans \leq \left \lfloor \frac{mx}{10} \right \rfloor ans10mx

同理,当 x < 0 x \lt 0 x<0 时,不等式成立的充要条件是 a n s ≥ ⌊ m i 10 ⌋ ans \geq \left \lfloor \frac{mi}{10} \right \rfloor ans10mi

因此,我们可以通过判断 a n s ans ans 是否在 [ ⌊ m i 10 ⌋ , ⌊ m x 10 ⌋ ] [\left \lfloor \frac{mi}{10} \right \rfloor, \left \lfloor \frac{mx}{10} \right \rfloor] [10mi,10mx] 的范围内来判断 a n s ans ans 是否溢出。若溢出,则返回 0 0 0。否则,将 y y y 添加到 a n s ans ans 的末尾,然后将 x x x 的最后一位数字去除,即 x ← ⌊ x 10 ⌋ x \gets \left \lfloor \frac{x}{10} \right \rfloor x10x

时间复杂度 O ( log ⁡ ∣ x ∣ ) O(\log |x|) O(logx),其中 ∣ x ∣ |x| x x x x 的绝对值。空间复杂度 O ( 1 ) O(1) O(1)

Python3
class Solution:
    def reverse(self, x: int) -> int:
        ans = 0
        mi, mx = -(2**31), 2**31 - 1
        while x:
            if ans < mi // 10 + 1 or ans > mx // 10:
                return 0
            y = x % 10
            if x < 0 and y > 0:
                y -= 10
            ans = ans * 10 + y
            x = (x - y) // 10
        return ans
Java
class Solution {
    public int reverse(int x) {
        int ans = 0;
        for (; x != 0; x /= 10) {
            if (ans < Integer.MIN_VALUE / 10 || ans > Integer.MAX_VALUE / 10) {
                return 0;
            }
            ans = ans * 10 + x % 10;
        }
        return ans;
    }
}
C++
class Solution {
public:
    int reverse(int x) {
        int ans = 0;
        for (; x; x /= 10) {
            if (ans < INT_MIN / 10 || ans > INT_MAX / 10) {
                return 0;
            }
            ans = ans * 10 + x % 10;
        }
        return ans;
    }
};

8. 字符串转换整数 (atoi)

题目描述

请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数。

函数 myAtoi(string s) 的算法如下:

  1. 空格:读入字符串并丢弃无用的前导空格(" "
  2. 符号:检查下一个字符(假设还未到字符末尾)为 '-' 还是 '+'。如果两者都不存在,则假定结果为正。
  3. 转换:通过跳过前置零来读取该整数,直到遇到非数字字符或到达字符串的结尾。如果没有读取数字,则结果为0。
  4. 舍入:如果整数数超过 32 位有符号整数范围 [−231,  231 − 1] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −231 的整数应该被舍入为 −231 ,大于 231 − 1 的整数应该被舍入为 231 − 1

返回整数作为最终结果。

 

示例 1:

输入:s = "42"

输出:42

解释:加粗的字符串为已经读入的字符,插入符号是当前读取的字符。

带下划线线的字符是所读的内容,插入符号是当前读入位置。
第 1 步:“42”(当前没有读入字符,因为没有前导空格)
^
第 2 步:“42”(当前没有读入字符,因为这里不存在 ‘-’ 或者 ‘+’)
^
第 3 步:“42”(读入 “42”)
^

示例 2:

输入:s = " -042"

输出:-42

解释:

第 1 步:“ -042”(读入前导空格,但忽视掉)
^
第 2 步:" -042"(读入 ‘-’ 字符,所以结果应该是负数)
^
第 3 步:" -042"(读入 “042”,在结果中忽略前导零)
^

示例 3:

输入:s = "1337c0d3"

输出:1337

解释:

第 1 步:“1337c0d3”(当前没有读入字符,因为没有前导空格)
^
第 2 步:“1337c0d3”(当前没有读入字符,因为这里不存在 ‘-’ 或者 ‘+’)
^
第 3 步:“1337c0d3”(读入 “1337”;由于下一个字符不是一个数字,所以读入停止)
^

示例 4:

输入:s = "0-1"

输出:0

解释:

第 1 步:“0-1” (当前没有读入字符,因为没有前导空格)
^
第 2 步:“0-1” (当前没有读入字符,因为这里不存在 ‘-’ 或者 ‘+’)
^
第 3 步:“0-1” (读入 “0”;由于下一个字符不是一个数字,所以读入停止)
^

示例 5:

输入:s = "words and 987"

输出:0

解释:

读取在第一个非数字字符“w”处停止。

 

提示:

  • 0 <= s.length <= 200
  • s 由英文字母(大写和小写)、数字(0-9)、' ''+''-''.' 组成

难度:中等

标签:字符串

解法

方法一:遍历字符串

我们首先判断字符串是否为空,如果是,直接返回 0 0 0

否则,我们需要遍历字符串,跳过前导空格,判断第一个非空格字符是否为正负号。

接着遍历后面的字符,如果是数字,我们判断添加该数字是否会导致整数溢出,如果会,我们根据正负号返回结果。否则我们将数字添加到结果中。继续遍历后面的字符,直到遇到非数字字符或者遍历结束。

遍历结束后,我们根据正负号返回结果。

时间复杂度 O ( n ) O(n) O(n),其中 n n n 为字符串的长度。我们只需要依次处理所有字符。空间复杂度 O ( 1 ) O(1) O(1)

面试题 67. 把字符串转换成整数

Python3
class Solution:
    def myAtoi(self, s: str) -> int:
        if not s:
            return 0
        n = len(s)
        if n == 0:
            return 0
        i = 0
        while s[i] == ' ':
            i += 1
            # 仅包含空格
            if i == n:
                return 0
        sign = -1 if s[i] == '-' else 1
        if s[i] in ['-', '+']:
            i += 1
        res, flag = 0, (2**31 - 1) // 10
        while i < n:
            # 非数字,跳出循环体
            if not s[i].isdigit():
                break
            c = int(s[i])
            # 溢出判断
            if res > flag or (res == flag and c > 7):
                return 2**31 - 1 if sign > 0 else -(2**31)
            res = res * 10 + c
            i += 1
        return sign * res
Java
class Solution {
    public int myAtoi(String s) {
        if (s == null) return 0;
        int n = s.length();
        if (n == 0) return 0;
        int i = 0;
        while (s.charAt(i) == ' ') {
            // 仅包含空格
            if (++i == n) return 0;
        }
        int sign = 1;
        if (s.charAt(i) == '-') sign = -1;
        if (s.charAt(i) == '-' || s.charAt(i) == '+') ++i;
        int res = 0, flag = Integer.MAX_VALUE / 10;
        for (; i < n; ++i) {
            // 非数字,跳出循环体
            if (s.charAt(i) < '0' || s.charAt(i) > '9') break;
            // 溢出判断
            if (res > flag || (res == flag && s.charAt(i) > '7'))
                return sign > 0 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
            res = res * 10 + (s.charAt(i) - '0');
        }
        return sign * res;
    }
}

9. 回文数

题目描述

给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false

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

  • 例如,121 是回文,而 123 不是。

 

示例 1:

输入:x = 121
输出:true

示例 2:

输入:x = -121
输出:false
解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。

示例 3:

输入:x = 10
输出:false
解释:从右向左读, 为 01 。因此它不是一个回文数。

 

提示:

  • -231 <= x <= 231 - 1

 

进阶:你能不将整数转为字符串来解决这个问题吗?

难度:简单

标签:数学

解法

方法一:反转一半数字

我们先判断特殊情况:

  • 如果 x < 0 x \lt 0 x<0,那么 x x x 不是回文数,直接返回 false
  • 如果 x > 0 x \gt 0 x>0 x x x 的个位数是 0 0 0,那么 x x x 不是回文数,直接返回 false
  • 如果 x x x 的个位数不是 0 0 0,那么 x x x 可能是回文数,继续执行下面的步骤。

我们将 x x x 的后半部分反转,与前半部分进行比较,如果相等,那么 x x x 是回文数,否则 x x x 不是回文数。

举个例子,例如 x = 1221 x = 1221 x=1221,我们可以将数字后半部分从 “21” 反转为 “12”,并将其与前半部分 “12” 进行比较,因为二者相等,我们得知数字 x x x 是回文。

让我们看看如何将后半部分反转。

对于数字 1221 1221 1221,如果执行 1221   m o d   10 1221 \bmod 10 1221mod10,我们将得到最后一位数字 1 1 1,要得到倒数第二位数字,我们可以先通过除以 10 10 10 将最后一位数字从 1221 1221 1221 中移除, 1221 / 10 = 122 1221 / 10 = 122 1221/10=122,再求出上一步结果除以 10 10 10 的余数, 122   m o d   10 = 2 122 \bmod 10 = 2 122mod10=2,就可以得到倒数第二位数字。

如果继续这个过程,我们将得到更多位数的反转数字。

通过将最后一位数字不断地累乘到取出数字的变量 y y y 上,我们可以得到以相反顺序的数字。

在代码实现上,我们可以反复“取出” x x x 的最后一位数字,并将其“添加”到 y y y 的后面,循环直到 y ≥ x y \ge x yx,如果此时 x = y x = y x=y,或者 x = y / 10 x = y / 10 x=y/10,那么 x x x 就是回文数。

时间复杂度 O ( log ⁡ 10 ( n ) ) O(\log_{10}(n)) O(log10(n)),其中 n n n x x x。对于每次迭代,我们会将输入除以 10 10 10,因此时间复杂度为 O ( log ⁡ 10 ( n ) ) O(\log_{10}(n)) O(log10(n))。空间复杂度 O ( 1 ) O(1) O(1)

Python3
class Solution:
    def isPalindrome(self, x: int) -> bool:
        if x < 0 or (x and x % 10 == 0):
            return False
        y = 0
        while y < x:
            y = y * 10 + x % 10
            x //= 10
        return x in (y, y // 10)
Java
class Solution {
    public boolean isPalindrome(int x) {
        if (x < 0 || (x > 0 && x % 10 == 0)) {
            return false;
        }
        int y = 0;
        for (; y < x; x /= 10) {
            y = y * 10 + x % 10;
        }
        return x == y || x == y / 10;
    }
}
C++
class Solution {
public:
    bool isPalindrome(int x) {
        if (x < 0 || (x && x % 10 == 0)) {
            return false;
        }
        int y = 0;
        for (; y < x; x /= 10) {
            y = y * 10 + x % 10;
        }
        return x == y || x == y / 10;
    }
};

10. 正则表达式匹配

题目描述

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。

  • '.' 匹配任意单个字符
  • '*' 匹配零个或多个前面的那一个元素

所谓匹配,是要涵盖 整个 字符串 s 的,而不是部分字符串。

 

示例 1:

输入:s = “aa”, p = “a”
输出:false
解释:“a” 无法匹配 “aa” 整个字符串。

示例 2:

输入:s = “aa”, p = “a*”
输出:true
解释:因为 ‘*’ 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 ‘a’。因此,字符串 “aa” 可被视为 ‘a’ 重复了一次。

示例 3:

输入:s = “ab”, p = “."
输出:true
解释:".
” 表示可匹配零个或多个(‘*’)任意字符(‘.’)。

 

提示:

  • 1 <= s.length <= 20
  • 1 <= p.length <= 20
  • s 只包含从 a-z 的小写字母。
  • p 只包含从 a-z 的小写字母,以及字符 . 和 *
  • 保证每次出现字符 * 时,前面都匹配到有效的字符

难度:困难

标签:递归,字符串,动态规划

解法

方法一:记忆化搜索

我们设计一个函数 d f s ( i , j ) dfs(i, j) dfs(i,j),表示从 s s s 的第 i i i 个字符开始,和 p p p 的第 j j j 个字符开始是否匹配。那么答案就是 d f s ( 0 , 0 ) dfs(0, 0) dfs(0,0)

函数 d f s ( i , j ) dfs(i, j) dfs(i,j) 的计算过程如下:

  • 如果 j j j 已经到达 p p p 的末尾,那么如果 i i i 也到达了 s s s 的末尾,那么匹配成功,否则匹配失败。
  • 如果 j j j 的下一个字符是 '*',我们可以选择匹配 0 0 0 s [ i ] s[i] s[i] 字符,那么就是 d f s ( i , j + 2 ) dfs(i, j + 2) dfs(i,j+2)。如果此时 i < m i \lt m i<m 并且 s [ i ] s[i] s[i] p [ j ] p[j] p[j] 匹配,那么我们可以选择匹配 1 1 1 s [ i ] s[i] s[i] 字符,那么就是 d f s ( i + 1 , j ) dfs(i + 1, j) dfs(i+1,j)
  • 如果 j j j 的下一个字符不是 '*',那么如果 i < m i \lt m i<m 并且 s [ i ] s[i] s[i] p [ j ] p[j] p[j] 匹配,那么就是 d f s ( i + 1 , j + 1 ) dfs(i + 1, j + 1) dfs(i+1,j+1)。否则匹配失败。

过程中,我们可以使用记忆化搜索,避免重复计算。

时间复杂度 O ( m × n ) O(m \times n) O(m×n),空间复杂度 O ( m × n ) O(m \times n) O(m×n)。其中 m m m n n n 分别是 s s s p p p 的长度。

Python3
class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        @cache
        def dfs(i, j):
            if j >= n:
                return i == m
            if j + 1 < n and p[j + 1] == '*':
                return dfs(i, j + 2) or (
                    i < m and (s[i] == p[j] or p[j] == '.') and dfs(i + 1, j)
                )
            return i < m and (s[i] == p[j] or p[j] == '.') and dfs(i + 1, j + 1)

        m, n = len(s), len(p)
        return dfs(0, 0)
Java
class Solution {
    private Boolean[][] f;
    private String s;
    private String p;
    private int m;
    private int n;

    public boolean isMatch(String s, String p) {
        m = s.length();
        n = p.length();
        f = new Boolean[m + 1][n + 1];
        this.s = s;
        this.p = p;
        return dfs(0, 0);
    }

    private boolean dfs(int i, int j) {
        if (j >= n) {
            return i == m;
        }
        if (f[i][j] != null) {
            return f[i][j];
        }
        boolean res = false;
        if (j + 1 < n && p.charAt(j + 1) == '*') {
            res = dfs(i, j + 2)
                || (i < m && (s.charAt(i) == p.charAt(j) || p.charAt(j) == '.') && dfs(i + 1, j));
        } else {
            res = i < m && (s.charAt(i) == p.charAt(j) || p.charAt(j) == '.') && dfs(i + 1, j + 1);
        }
        return f[i][j] = res;
    }
}
C++
class Solution {
public:
    bool isMatch(string s, string p) {
        int m = s.size(), n = p.size();
        int f[m + 1][n + 1];
        memset(f, 0, sizeof f);
        function<bool(int, int)> dfs = [&](int i, int j) -> bool {
            if (j >= n) {
                return i == m;
            }
            if (f[i][j]) {
                return f[i][j] == 1;
            }
            int res = -1;
            if (j + 1 < n && p[j + 1] == '*') {
                if (dfs(i, j + 2) or (i < m and (s[i] == p[j] or p[j] == '.') and dfs(i + 1, j))) {
                    res = 1;
                }
            } else if (i < m and (s[i] == p[j] or p[j] == '.') and dfs(i + 1, j + 1)) {
                res = 1;
            }
            f[i][j] = res;
            return res == 1;
        };
        return dfs(0, 0);
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值