Leetcode: 0021-0030题速览

Leetcode: 0021-0030题速览

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

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

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

目录

21. 合并两个有序链表

题目描述

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例 1:

输入: l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

示例 2:

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

示例 3:

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

提示:

  • 两个链表的节点数目范围是 [0, 50]
  • -100 <= Node.val <= 100
  • l1l2 均按 非递减顺序 排列

难度:简单

标签:递归,链表

解法

方法一:递归

我们先判断链表 l 1 l_1 l1 和 l 2 l_2 l2 是否为空,若其中一个为空,则返回另一个链表。否则,我们比较 l 1 l_1 l1 和 l 2 l_2 l2 的头节点:

  • 若 l 1 l_1 l1 的头节点的值小于等于 l 2 l_2 l2 的头节点的值,则递归调用函数 m e r g e T w o L i s t s ( l 1 . n e x t , l 2 ) mergeTwoLists(l_1.next, l_2) mergeTwoLists(l1.next,l2),并将 l 1 l_1 l1 的头节点与返回的链表头节点相连,返回 l 1 l_1 l1 的头节点。
  • 否则,递归调用函数 m e r g e T w o L i s t s ( l 1 , l 2 . n e x t ) mergeTwoLists(l_1, l_2.next) mergeTwoLists(l1,l2.next),并将 l 2 l_2 l2 的头节点与返回的链表头节点相连,返回 l 2 l_2 l2 的头节点。

时间复杂度 O ( m + n ) O(m + n) O(m+n),空间复杂度 O ( m + n ) O(m + n) O(m+n)。其中 m m m 和 n n n 分别为两个链表的长度。

Python3
python 复制代码
## Definition for singly-linked list.
## class ListNode:
##     def __init__(self, val=0, next=None):
##         self.val = val
##         self.next = next
class Solution:
    def mergeTwoLists(
        self, list1: Optional[ListNode], list2: Optional[ListNode]
    ) -> Optional[ListNode]:
        if list1 is None or list2 is None:
            return list1 or list2
        if list1.val <= list2.val:
            list1.next = self.mergeTwoLists(list1.next, list2)
            return list1
        else:
            list2.next = self.mergeTwoLists(list1, list2.next)
            return list2
Java
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 mergeTwoLists(ListNode list1, ListNode list2) {
        if (list1 == null) {
            return list2;
        }
        if (list2 == null) {
            return list1;
        }
        if (list1.val <= list2.val) {
            list1.next = mergeTwoLists(list1.next, list2);
            return list1;
        } else {
            list2.next = mergeTwoLists(list1, list2.next);
            return list2;
        }
    }
}
C++
cpp 复制代码
/**
 * 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* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if (!list1) return list2;
        if (!list2) return list1;
        if (list1->val <= list2->val) {
            list1->next = mergeTwoLists(list1->next, list2);
            return list1;
        } else {
            list2->next = mergeTwoLists(list1, list2->next);
            return list2;
        }
    }
};

22. 括号生成

题目描述

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的括号组合。

示例 1:

输入: n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]

示例 2:

输入: n = 1
输出:["()"]

提示:

  • 1 <= n <= 8

难度:中等

标签:字符串,动态规划,回溯

解法

方法一:DFS + 剪枝

题目中 n n n 的范围为 [ 1 , 8 ] [1, 8] [1,8],因此我们直接通过"暴力搜索 + 剪枝"的方式通过本题。

我们设计一个函数 d f s ( l , r , t ) dfs(l, r, t) dfs(l,r,t),其中 l l l 和 r r r 分别表示左括号和右括号的数量,而 t t t 表示当前的括号序列。那么我们可以得到如下的递归结构:

  • 如果 l > n l \gt n l>n 或者 r > n r \gt n r>n 或者 l < r l \lt r l<r,那么当前括号组合 t t t 不合法,直接返回;
  • 如果 l = n l = n l=n 且 r = n r = n r=n,那么当前括号组合 t t t 合法,将其加入答案数组 ans 中,直接返回;
  • 我们可以选择添加一个左括号,递归执行 dfs(l + 1, r, t + "(")
  • 我们也可以选择添加一个右括号,递归执行 dfs(l, r + 1, t + ")")

时间复杂度 O ( 2 n × 2 × n ) O(2^{n\times 2} \times n) O(2n×2×n),空间复杂度 O ( n ) O(n) O(n)。

Python3
python 复制代码
class Solution:
    def generateParenthesis(self, n: int) -> List[str]:
        def dfs(l, r, t):
            if l > n or r > n or l < r:
                return
            if l == n and r == n:
                ans.append(t)
                return
            dfs(l + 1, r, t + '(')
            dfs(l, r + 1, t + ')')

        ans = []
        dfs(0, 0, '')
        return ans
Java
java 复制代码
class Solution {
    private List<String> ans = new ArrayList<>();
    private int n;

    public List<String> generateParenthesis(int n) {
        this.n = n;
        dfs(0, 0, "");
        return ans;
    }

    private void dfs(int l, int r, String t) {
        if (l > n || r > n || l < r) {
            return;
        }
        if (l == n && r == n) {
            ans.add(t);
            return;
        }
        dfs(l + 1, r, t + "(");
        dfs(l, r + 1, t + ")");
    }
}
C++
cpp 复制代码
class Solution {
public:
    vector<string> generateParenthesis(int n) {
        vector<string> ans;
        function<void(int, int, string)> dfs = [&](int l, int r, string t) {
            if (l > n || r > n || l < r) return;
            if (l == n && r == n) {
                ans.push_back(t);
                return;
            }
            dfs(l + 1, r, t + "(");
            dfs(l, r + 1, t + ")");
        };
        dfs(0, 0, "");
        return ans;
    }
};

23. 合并 K 个升序链表

题目描述

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例 1:

输入: lists = [[1,4,5],[1,3,4],[2,6]]
输出: [1,1,2,3,4,4,5,6]
解释: 链表数组如下:

[

1->4->5,

1->3->4,

2->6

]

将它们合并到一个有序链表中得到。

1->1->2->3->4->4->5->6

示例 2:

输入: lists = []
输出:[]

示例 3:

输入: lists = [[]]
输出:[]

提示:

  • k == lists.length
  • 0 <= k <= 10^4
  • 0 <= lists[i].length <= 500
  • -10^4 <= lists[i][j] <= 10^4
  • lists[i]升序 排列
  • lists[i].length 的总和不超过 10^4

难度:困难

标签:链表,分治,堆(优先队列),归并排序

解法

方法一:优先队列(小根堆)

我们可以创建一个小根堆来 p q pq pq 维护所有链表的头节点,每次从小根堆中取出值最小的节点,添加到结果链表的末尾,然后将该节点的下一个节点加入堆中,重复上述步骤直到堆为空。

时间复杂度 O ( n × log ⁡ k ) O(n \times \log k) O(n×logk),空间复杂度 O ( k ) O(k) O(k)。其中 n n n 是所有链表节点数目的总和,而 k k k 是题目给定的链表数目。

Python3
python 复制代码
## Definition for singly-linked list.
## class ListNode:
##     def __init__(self, val=0, next=None):
##         self.val = val
##         self.next = next
class Solution:
    def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
        setattr(ListNode, "__lt__", lambda a, b: a.val < b.val)
        pq = [head for head in lists if head]
        heapify(pq)
        dummy = cur = ListNode()
        while pq:
            node = heappop(pq)
            if node.next:
                heappush(pq, node.next)
            cur.next = node
            cur = cur.next
        return dummy.next
Java
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 mergeKLists(ListNode[] lists) {
        PriorityQueue<ListNode> pq = new PriorityQueue<>((a, b) -> a.val - b.val);
        for (ListNode head : lists) {
            if (head != null) {
                pq.offer(head);
            }
        }
        ListNode dummy = new ListNode();
        ListNode cur = dummy;
        while (!pq.isEmpty()) {
            ListNode node = pq.poll();
            if (node.next != null) {
                pq.offer(node.next);
            }
            cur.next = node;
            cur = cur.next;
        }
        return dummy.next;
    }
}
C++
cpp 复制代码
/**
 * 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* mergeKLists(vector<ListNode*>& lists) {
        auto cmp = [](ListNode* a, ListNode* b) { return a->val > b->val; };
        priority_queue<ListNode*, vector<ListNode*>, decltype(cmp)> pq;
        for (auto head : lists) {
            if (head) {
                pq.push(head);
            }
        }
        ListNode* dummy = new ListNode();
        ListNode* cur = dummy;
        while (!pq.empty()) {
            ListNode* node = pq.top();
            pq.pop();
            if (node->next) {
                pq.push(node->next);
            }
            cur->next = node;
            cur = cur->next;
        }
        return dummy->next;
    }
};

24. 两两交换链表中的节点

题目描述

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

示例 1:

输入: head = [1,2,3,4]
输出:[2,1,4,3]

示例 2:

输入: head = []
输出:[]

示例 3:

输入: head = [1]
输出:[1]

提示:

  • 链表中节点的数目在范围 [0, 100]
  • 0 <= Node.val <= 100

难度:中等

标签:递归,链表

解法

方法一:递归

我们可以通过递归的方式实现两两交换链表中的节点。

递归的终止条件是链表中没有节点,或者链表中只有一个节点,此时无法进行交换,直接返回该节点。

否则,我们递归交换链表 h e a d . n e x t . n e x t head.next.next head.next.next,记交换后的头节点为 t t t,然后我们记 h e a d head head 的下一个节点为 p p p,然后令 p p p 指向 h e a d head head,而 h e a d head head 指向 t t t,最后返回 p p p。

时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n),其中 n n n 是链表的长度。

Python3
python 复制代码
## Definition for singly-linked list.
## class ListNode:
##     def __init__(self, val=0, next=None):
##         self.val = val
##         self.next = next
class Solution:
    def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if head is None or head.next is None:
            return head
        t = self.swapPairs(head.next.next)
        p = head.next
        p.next = head
        head.next = t
        return p
Java
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 swapPairs(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode t = swapPairs(head.next.next);
        ListNode p = head.next;
        p.next = head;
        head.next = t;
        return p;
    }
}
C++
cpp 复制代码
/**
 * 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* swapPairs(ListNode* head) {
        if (!head || !head->next) {
            return head;
        }
        ListNode* t = swapPairs(head->next->next);
        ListNode* p = head->next;
        p->next = head;
        head->next = t;
        return p;
    }
};

25. K 个一组翻转链表

题目描述

给你链表的头节点 head ,每 k个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例 1:

输入: head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]

示例 2:

输入: head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]

提示:

  • 链表中的节点数目为 n
  • 1 <= k <= n <= 5000
  • 0 <= Node.val <= 1000

进阶: 你可以设计一个只用 O(1) 额外内存空间的算法解决此问题吗?

难度:困难

标签:递归,链表

解法

方法一:迭代

时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( 1 ) O(1) O(1),其中 n n n 是链表的长度。

Python3
python 复制代码
## Definition for singly-linked list.
## class ListNode:
##     def __init__(self, val=0, next=None):
##         self.val = val
##         self.next = next
class Solution:
    def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
        def reverseList(head):
            pre, p = None, head
            while p:
                q = p.next
                p.next = pre
                pre = p
                p = q
            return pre

        dummy = ListNode(next=head)
        pre = cur = dummy
        while cur.next:
            for _ in range(k):
                cur = cur.next
                if cur is None:
                    return dummy.next
            t = cur.next
            cur.next = None
            start = pre.next
            pre.next = reverseList(start)
            start.next = t
            pre = start
            cur = pre
        return dummy.next
Java
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 reverseKGroup(ListNode head, int k) {
        ListNode dummy = new ListNode(0, head);
        ListNode pre = dummy, cur = dummy;
        while (cur.next != null) {
            for (int i = 0; i < k && cur != null; ++i) {
                cur = cur.next;
            }
            if (cur == null) {
                return dummy.next;
            }
            ListNode t = cur.next;
            cur.next = null;
            ListNode start = pre.next;
            pre.next = reverseList(start);
            start.next = t;
            pre = start;
            cur = pre;
        }
        return dummy.next;
    }

    private ListNode reverseList(ListNode head) {
        ListNode pre = null, p = head;
        while (p != null) {
            ListNode q = p.next;
            p.next = pre;
            pre = p;
            p = q;
        }
        return pre;
    }
}

26. 删除有序数组中的重复项

题目描述

给你一个 非严格递增排列 的数组 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 * 10^4^
  • -10^4^ <= nums[i] <= 10^4^
  • nums 已按 非严格递增 排列

难度:简单

标签:数组,双指针

解法

方法一:一次遍历

我们用一个变量 k k k 记录当前已经处理好的数组的长度,初始时 k = 0 k=0 k=0,表示空数组。

然后我们从左到右遍历数组,对于遍历到的每个元素 x x x,如果 k = 0 k=0 k=0 或者 x ≠ n u m s [ k − 1 ] x \neq nums[k-1] x=nums[k−1],我们就将 x x x 放到 n u m s [ k ] nums[k] nums[k] 的位置,然后 k k k 自增 1 1 1。否则, x x x 与 n u m s [ k − 1 ] nums[k-1] nums[k−1] 相同,我们直接跳过这个元素。继续遍历,直到遍历完整个数组。

这样,当遍历结束时, n u m s nums nums 中前 k k k 个元素就是我们要求的答案,且 k k k 就是答案的长度。

时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)。其中 n n n 为数组的长度。

补充:

原问题要求最多相同的数字最多出现 1 1 1 次,我们可以扩展至相同的数字最多保留 k k k 个。

  • 由于相同的数字最多保留 k k k 个,那么原数组的前 k k k 个元素我们可以直接保留;
  • 对于后面的数字,能够保留的前提是:当前数字 x x x 与前面已保留的数字的倒数第 k k k 个元素比较,不同则保留,相同则跳过。

相似题目:

Python3
python 复制代码
class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        k = 0
        for x in nums:
            if k == 0 or x != nums[k - 1]:
                nums[k] = x
                k += 1
        return k
Java
java 复制代码
class Solution {
    public int removeDuplicates(int[] nums) {
        int k = 0;
        for (int x : nums) {
            if (k == 0 || x != nums[k - 1]) {
                nums[k++] = x;
            }
        }
        return k;
    }
}
C++
cpp 复制代码
class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int k = 0;
        for (int x : nums) {
            if (k == 0 || x != nums[k - 1]) {
                nums[k++] = x;
            }
        }
        return k;
    }
};

27. 移除元素

题目描述

给你一个数组 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

难度:简单

标签:数组,双指针

解法

方法一:一次遍历

我们用变量 k k k 记录当前不等于 v a l val val 的元素个数。

遍历数组 n u m s nums nums,如果当前元素 x x x 不等于 v a l val val,则将 x x x 赋值给 n u m s [ k ] nums[k] nums[k],并将 k k k 自增 1 1 1。

最后返回 k k k 即可。

时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)。其中 n n n 为数组 n u m s nums nums 的长度。

Python3
python 复制代码
class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        k = 0
        for x in nums:
            if x != val:
                nums[k] = x
                k += 1
        return k
Java
java 复制代码
class Solution {
    public int removeElement(int[] nums, int val) {
        int k = 0;
        for (int x : nums) {
            if (x != val) {
                nums[k++] = x;
            }
        }
        return k;
    }
}
C++
cpp 复制代码
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int k = 0;
        for (int x : nums) {
            if (x != val) {
                nums[k++] = x;
            }
        }
        return k;
    }
};

28. 找出字符串中第一个匹配项的下标

题目描述

给你两个字符串 haystackneedle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1

示例 1:

输入: haystack = "sadbutsad", needle = "sad"
输出: 0
解释: "sad" 在下标 0 和 6 处匹配。

第一个匹配项的下标是 0 ,所以返回 0 。

示例 2:

输入: haystack = "leetcode", needle = "leeto"
输出: -1
解释:"leeto" 没有在 "leetcode" 中出现,所以返回 -1 。

提示:

  • 1 <= haystack.length, needle.length <= 10^4^
  • haystackneedle 仅由小写英文字符组成

难度:简单

标签:双指针,字符串,字符串匹配

解法

方法一:遍历

以字符串 haystack 的每一个字符为起点与字符串 needle 进行比较,若发现能够匹配的索引,直接返回即可。

假设字符串 haystack 长度为 n n n,字符串 needle 长度为 m m m,则时间复杂度为 O ( ( n − m ) × m ) O((n-m) \times m) O((n−m)×m),空间复杂度 O ( 1 ) O(1) O(1)。

Python3
python 复制代码
class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
        n, m = len(haystack), len(needle)
        for i in range(n - m + 1):
            if haystack[i : i + m] == needle:
                return i
        return -1
Java
java 复制代码
class Solution {
    public int strStr(String haystack, String needle) {
        if ("".equals(needle)) {
            return 0;
        }

        int len1 = haystack.length();
        int len2 = needle.length();
        int p = 0;
        int q = 0;
        while (p < len1) {
            if (haystack.charAt(p) == needle.charAt(q)) {
                if (len2 == 1) {
                    return p;
                }
                ++p;
                ++q;
            } else {
                p -= q - 1;
                q = 0;
            }

            if (q == len2) {
                return p - q;
            }
        }
        return -1;
    }
}
C++
cpp 复制代码
class Solution {
private:
    vector<int> Next(string str) {
        vector<int> n(str.length());
        n[0] = -1;
        int i = 0, pre = -1;
        int len = str.length();
        while (i < len) {
            while (pre >= 0 && str[i] != str[pre])
                pre = n[pre];
            ++i, ++pre;
            if (i >= len)
                break;
            if (str[i] == str[pre])
                n[i] = n[pre];
            else
                n[i] = pre;
        }
        return n;
    }

public:
    int strStr(string haystack, string needle) {
        if (0 == needle.length())
            return 0;

        vector<int> n(Next(needle));

        int len = haystack.length() - needle.length() + 1;
        for (int i = 0; i < len; ++i) {
            int j = 0, k = i;
            while (j < needle.length() && k < haystack.length()) {
                if (haystack[k] != needle[j]) {
                    if (n[j] >= 0) {
                        j = n[j];
                        continue;
                    } else
                        break;
                }
                ++k, ++j;
            }
            if (j >= needle.length())
                return k - j;
        }

        return -1;
    }
};

29. 两数相除

题目描述

给你两个整数,被除数 dividend 和除数 divisor。将两数相除,要求 不使用 乘法、除法和取余运算。

整数除法应该向零截断,也就是截去(truncate)其小数部分。例如,8.345 将被截断为 8-2.7335 将被截断至 -2

返回被除数 dividend 除以除数 divisor 得到的

注意: 假设我们的环境只能存储 32 位 有符号整数,其数值范围是 [−2^31^, 2^31^− 1] 。本题中,如果商 严格大于 2^31^− 1 ,则返回 2^31^− 1 ;如果商 严格小于 -2^31^ ,则返回 -2^31^。

示例 1:

输入: dividend = 10, divisor = 3
输出: 3
**解释:**10/3 = 3.33333... ,向零截断后得到 3 。

示例 2:

输入: dividend = 7, divisor = -3
输出: -2
解释: 7/-3 = -2.33333... ,向零截断后得到 -2 。

提示:

  • -2^31^ <= dividend, divisor <= 2^31^ - 1
  • divisor != 0

难度:中等

标签:位运算,数学

解法

方法一:模拟 + 快速幂

除法本质上就是减法,题目要求我们计算出两个数相除之后的取整结果,其实就是计算被除数是多少个除数加上一个小于除数的数构成的。但是一次循环只能做一次减法,效率太低会导致超时,可借助快速幂的思想进行优化。

需要注意的是,由于题目明确要求最大只能使用 32 位有符号整数,所以需要将除数和被除数同时转换为负数进行计算。因为转换正数可能会导致溢出,如当被除数为 INT32_MIN 时,转换为正数时会大于 INT32_MAX

假设被除数为 a a a,除数为 b b b,则时间复杂度为 O ( log ⁡ a × log ⁡ b ) O(\log a \times \log b) O(loga×logb),空间复杂度 O ( 1 ) O(1) O(1)。

Python3
python 复制代码
class Solution:
    def divide(self, a: int, b: int) -> int:
        if b == 1:
            return a
        if a == -(2**31) and b == -1:
            return 2**31 - 1
        sign = (a > 0 and b > 0) or (a < 0 and b < 0)
        a = -a if a > 0 else a
        b = -b if b > 0 else b
        ans = 0
        while a <= b:
            x = b
            cnt = 1
            while x >= (-(2**30)) and a <= (x << 1):
                x <<= 1
                cnt <<= 1
            a -= x
            ans += cnt
        return ans if sign else -ans
Java
java 复制代码
class Solution {
    public int divide(int a, int b) {
        if (b == 1) {
            return a;
        }
        if (a == Integer.MIN_VALUE && b == -1) {
            return Integer.MAX_VALUE;
        }
        boolean sign = (a > 0 && b > 0) || (a < 0 && b < 0);
        a = a > 0 ? -a : a;
        b = b > 0 ? -b : b;
        int ans = 0;
        while (a <= b) {
            int x = b;
            int cnt = 1;
            while (x >= (Integer.MIN_VALUE >> 1) && a <= (x << 1)) {
                x <<= 1;
                cnt <<= 1;
            }
            ans += cnt;
            a -= x;
        }
        return sign ? ans : -ans;
    }
}
C++
cpp 复制代码
class Solution {
public:
    int divide(int a, int b) {
        if (b == 1) {
            return a;
        }
        if (a == INT_MIN && b == -1) {
            return INT_MAX;
        }
        bool sign = (a > 0 && b > 0) || (a < 0 && b < 0);
        a = a > 0 ? -a : a;
        b = b > 0 ? -b : b;
        int ans = 0;
        while (a <= b) {
            int x = b;
            int cnt = 1;
            while (x >= (INT_MIN >> 1) && a <= (x << 1)) {
                x <<= 1;
                cnt <<= 1;
            }
            ans += cnt;
            a -= x;
        }
        return sign ? ans : -ans;
    }
};

30. 串联所有单词的子串

题目描述

给定一个字符串 s和一个字符串数组 words words 中所有字符串 长度相同

s中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。

  • 例如,如果 words = ["ab","cd","ef"], 那么 "abcdef""abefcd""cdabef""cdefab""efabcd", 和 "efcdab" 都是串联子串。 "acdbef" 不是串联子串,因为他不是任何 words 排列的连接。

返回所有串联子串在 s中的开始索引。你可以以 任意顺序 返回答案。

示例 1:

输入: s = "barfoothefoobarman", words = ["foo","bar"]
输出: [0,9]
解释: 因为 words.length == 2 同时 words[i].length == 3,连接的子字符串的长度必须为 6。

子串 "barfoo" 开始位置是 0。它是 words 中以 ["bar","foo"] 顺序排列的连接。

子串 "foobar" 开始位置是 9。它是 words 中以 ["foo","bar"] 顺序排列的连接。

输出顺序无关紧要。返回 [9,0] 也是可以的。

示例 2:

输入: s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"]
输出:[]
解释: 因为words.length == 4 并且 words[i].length == 4,所以串联子串的长度必须为 16。

s 中没有子串长度为 16 并且等于 words 的任何顺序排列的连接。

所以我们返回一个空数组。

示例 3:

输入: s = "barfoofoobarthefoobarman", words = ["bar","foo","the"]
输出: [6,9,12]
解释: 因为 words.length == 3 并且 words[i].length == 3,所以串联子串的长度必须为 9。

子串 "foobarthe" 开始位置是 6。它是 words 中以 ["foo","bar","the"] 顺序排列的连接。

子串 "barthefoo" 开始位置是 9。它是 words 中以 ["bar","the","foo"] 顺序排列的连接。

子串 "thefoobar" 开始位置是 12。它是 words 中以 ["the","foo","bar"] 顺序排列的连接。

提示:

  • 1 <= s.length <= 10^4^
  • 1 <= words.length <= 5000
  • 1 <= words[i].length <= 30
  • words[i]s 由小写英文字母组成

难度:困难

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

解法

方法一:哈希表 + 滑动窗口

我们用哈希表 c n t cnt cnt 统计 w o r d s words words 中每个单词出现的次数,用哈希表 c n t 1 cnt1 cnt1 统计当前滑动窗口中每个单词出现的次数。我们记字符串 s s s 的长度为 m m m,字符串数组 w o r d s words words 中单词的数量为 n n n,每个单词的长度为 k k k。

我们可以枚举滑动窗口的起点 i i i,其中 0 < i < k 0 \lt i \lt k 0<i<k。对于每个起点,我们维护一个滑动窗口,左边界为 l l l,右边界为 r r r,滑动窗口中的单词个数为 t t t,另外用一个哈希表 c n t 1 cnt1 cnt1 统计滑动窗口中每个单词出现的次数。

每一次,我们提取字符串 s [ r : r + k ] s[r:r+k] s[r:r+k],如果 s [ r : r + k ] s[r:r+k] s[r:r+k] 不在哈希表 c n t cnt cnt 中,说明当前滑动窗口中的单词不合法,我们将左边界 l l l 更新为 r r r,同时将哈希表 c n t 1 cnt1 cnt1 清空,单词个数 t t t 重置为 0。如果 s [ r : r + k ] s[r:r+k] s[r:r+k] 在哈希表 c n t cnt cnt 中,说明当前滑动窗口中的单词合法,我们将单词个数 t t t 加 1,将哈希表 c n t 1 cnt1 cnt1 中 s [ r : r + k ] s[r:r+k] s[r:r+k] 的次数加 1。如果 c n t 1 [ s [ r : r + k ] ] cnt1[s[r:r+k]] cnt1[s[r:r+k]] 大于 c n t [ s [ r : r + k ] ] cnt[s[r:r+k]] cnt[s[r:r+k]],说明当前滑动窗口中 s [ r : r + k ] s[r:r+k] s[r:r+k] 出现的次数过多,我们需要将左边界 l l l 右移,直到 c n t 1 [ s [ r : r + k ] ] = c n t [ s [ r : r + k ] ] cnt1[s[r:r+k]] = cnt[s[r:r+k]] cnt1[s[r:r+k]]=cnt[s[r:r+k]]。如果 t = n t = n t=n,说明当前滑动窗口中的单词正好合法,我们将左边界 l l l 加入答案数组。

时间复杂度 O ( m × k ) O(m \times k) O(m×k),空间复杂度 O ( n × k ) O(n \times k) O(n×k)。其中 m m m 和 n n n 分别是字符串 s s s 和字符串数组 w o r d s words words 的长度,而 k k k 是字符串数组 w o r d s words words 中单词的长度。

Python3
python 复制代码
class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        cnt = Counter(words)
        m, n = len(s), len(words)
        k = len(words[0])
        ans = []
        for i in range(k):
            cnt1 = Counter()
            l = r = i
            t = 0
            while r + k <= m:
                w = s[r : r + k]
                r += k
                if w not in cnt:
                    l = r
                    cnt1.clear()
                    t = 0
                    continue
                cnt1[w] += 1
                t += 1
                while cnt1[w] > cnt[w]:
                    remove = s[l : l + k]
                    l += k
                    cnt1[remove] -= 1
                    t -= 1
                if t == n:
                    ans.append(l)
        return ans
Java
java 复制代码
class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        Map<String, Integer> cnt = new HashMap<>();
        for (String w : words) {
            cnt.merge(w, 1, Integer::sum);
        }
        int m = s.length(), n = words.length;
        int k = words[0].length();
        List<Integer> ans = new ArrayList<>();
        for (int i = 0; i < k; ++i) {
            Map<String, Integer> cnt1 = new HashMap<>();
            int l = i, r = i;
            int t = 0;
            while (r + k <= m) {
                String w = s.substring(r, r + k);
                r += k;
                if (!cnt.containsKey(w)) {
                    cnt1.clear();
                    l = r;
                    t = 0;
                    continue;
                }
                cnt1.merge(w, 1, Integer::sum);
                ++t;
                while (cnt1.get(w) > cnt.get(w)) {
                    String remove = s.substring(l, l + k);
                    l += k;
                    cnt1.merge(remove, -1, Integer::sum);
                    --t;
                }
                if (t == n) {
                    ans.add(l);
                }
            }
        }
        return ans;
    }
}
C++
cpp 复制代码
class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
相关推荐
小王子0091 小时前
链式前向星(最通俗易懂的讲解)
算法
通信仿真实验室1 小时前
(13)MATLAB莱斯(Rician)衰落信道仿真3
开发语言·人工智能·算法·matlab
Tisfy2 小时前
LeetCode 0983.最低票价:记忆化搜索
算法·leetcode·题解·记忆化搜索·哈希表
丁丁Tinsley11172 小时前
数据结构——顺序表(基础代码题)
c语言·数据结构·算法
xiao_fwuu2 小时前
LeetCode 918. 环形子数组的最大和
算法·leetcode·职场和发展
2401_857297913 小时前
秋招内推--招联金融2025
java·前端·算法·金融·求职招聘
闲人编程3 小时前
使用Python实现图形学的阴影贴图算法
python·算法·图形学·贴图·阴影贴图
MarisTang3 小时前
Go语言实现随机森林 (Random Forest)算法
算法·随机森林·golang
育种数据分析之放飞自我4 小时前
GWAS分析中显著位点如何注释基因:excel???
linux·算法·excel
碧海蓝天20225 小时前
C++ 线性表、内存操作、 迭代器,数据与算法分离。
开发语言·数据结构·c++·算法