文章目录
1. 无重复字符的最长子串【中等】
题目链接:https://leetcode.cn/problems/longest-substring-without-repeating-characters/
参考题解:https://leetcode.cn/problems/longest-substring-without-repeating-characters/solution/wu-zhong-fu-zi-fu-de-zui-chang-zi-chuan-by-leetc-2/
1.1 题目描述
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
示例1:
输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例2:
输入: s = “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例3:
输入: s = “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
提示:
- 0 <= s.length <= 5 * 10^4
- s 由英文字母、数字、符号和空格组成
1.2 解题思路
依次考察以字符串中每个字符 s[i] 为起始的最长子串,一个关键的性质是,随着 i 的增加,其最长子串的结束位置也在不断增加。具体来说,使用一个哈希表 repeat 来存不重复的字符,每次往 repeat 中去掉子串最左边的字符,然后再不断试探能否往右继续把子串加长,最后比较当前子串是否是历史最长子串。
1.3 代码实现
C++:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_set<char> repeat;
int r = -1;
int ans = 0;
int n = s.length();
for (int i = 0; i < n; ++i) {
if (i != 0) {
repeat.erase(s[i - 1]);
}
while (r + 1 < n && !repeat.count(s[r + 1])) {
repeat.insert(s[r + 1]);
++r;
}
ans = max(ans, r - i + 1);
}
return ans;
}
};
Golang:
func lengthOfLongestSubstring(s string) int {
n, right, ans := len(s), -1, 0
repeated := map[byte]int{}
for i := 0; i < n; i++ {
if i != 0 {
delete(repeated, s[i - 1])
}
for (right + 1) < n && repeated[s[right + 1]] == 0 {
right++
repeated[s[right]]++
}
ans = max(ans, right - i + 1)
}
return ans
}
func max(x, y int) int {
if x < y {
return y
}
return x
}
2. 反转链表【简单】
题目链接:https://leetcode.cn/problems/reverse-linked-list/
参考题解:https://leetcode.cn/problems/reverse-linked-list/solution/fan-zhuan-lian-biao-by-leetcode-solution-d1k2/
2.1 题目描述
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例2:
输入:head = [1,2]
输出:[2,1]
示例3:
输入:head = []
输出:[]
提示:
- 链表中节点的数目范围是 [0, 5000]
- -5000 <= Node.val <= 5000
2.2 解题思路
可以模拟一下反转的过程,捋清楚各个操作的先后顺序。为了实现某个节点 current 的反转,需要记录其前置节点,而在 current 反转后,其后继节点便丢失了,因此需要用一个临时节点 tmp 把 current 的后继节点暂时记录下来。
2.3 代码实现
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* reverseList(ListNode* head) {
ListNode* pre = nullptr;
ListNode* current = head;
while (current != nullptr) {
ListNode* tmp = current -> next;
current -> next = pre;
pre = current;
current = tmp;
}
return pre;
}
};
Golang:
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func reverseList(head *ListNode) *ListNode {
var pre *ListNode
current := head
for current != nil {
tmp := current.Next
current.Next = pre
pre = current
current = tmp
}
return pre
}
3. LRU 缓存【中等】
题目链接:https://leetcode.cn/problems/lru-cache/
参考题解:https://leetcode.cn/problems/lru-cache/solution/lruhuan-cun-ji-zhi-by-leetcode-solution/
3.1 题目描述
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache 类:
- LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
- int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
- void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。
函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。
示例:
输入
[“LRUCache”, “put”, “put”, “get”, “put”, “get”, “put”, “get”, “get”, “get”]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]
解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1); // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2); // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1); // 返回 -1 (未找到)
lRUCache.get(3); // 返回 3
lRUCache.get(4); // 返回 4
提示:
- 1 <= capacity <= 3000
- 0 <= key <= 10000
- 0 <= value <= 10^5
- 最多调用 2 * 10^5 次 get 和 put
3.2 解题思路
用双向链表模拟缓存,越靠近头部表示越近使用过,越靠近尾部表示越久未使用。为了快速找到缓存中的元素,需要用一个哈希表来存储各个节点的位置。
3.3 代码实现
官方题解代码:
C++:
// class LRUCache {
// public:
// LRUCache(int capacity) {
// }
// int get(int key) {
// }
// void put(int key, int value) {
// }
// };
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache* obj = new LRUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/
struct DLinkedNode {
int key, value;
DLinkedNode* prev;
DLinkedNode* next;
DLinkedNode(): key(0), value(0), prev(nullptr), next(nullptr) {}
DLinkedNode(int _key, int _value): key(_key), value(_value), prev(nullptr), next(nullptr) {}
};
class LRUCache {
private:
unordered_map<int, DLinkedNode*> cache;
DLinkedNode* head;
DLinkedNode* tail;
int size;
int capacity;
public:
LRUCache(int _capacity): capacity(_capacity), size(0) {
// 使用伪头部和伪尾部节点
head = new DLinkedNode();
tail = new DLinkedNode();
head->next = tail;
tail->prev = head;
}
int get(int key) {
if (!cache.count(key)) {
return -1;
}
// 如果 key 存在,先通过哈希表定位,再移到头部
DLinkedNode* node = cache[key];
moveToHead(node);
return node->value;
}
void put(int key, int value) {
if (!cache.count(key)) {
// 如果 key 不存在,创建一个新的节点
DLinkedNode* node = new DLinkedNode(key, value);
// 添加进哈希表
cache[key] = node;
// 添加至双向链表的头部
addToHead(node);
++size;
if (size > capacity) {
// 如果超出容量,删除双向链表的尾部节点
DLinkedNode* removed = removeTail();
// 删除哈希表中对应的项
cache.erase(removed->key);
// 防止内存泄漏
delete removed;
--size;
}
}
else {
// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
DLinkedNode* node = cache[key];
node->value = value;
moveToHead(node);
}
}
void addToHead(DLinkedNode* node) {
node->prev = head;
node->next = head->next;
head->next->prev = node;
head->next = node;
}
void removeNode(DLinkedNode* node) {
node->prev->next = node->next;
node->next->prev = node->prev;
}
void moveToHead(DLinkedNode* node) {
removeNode(node);
addToHead(node);
}
DLinkedNode* removeTail() {
DLinkedNode* node = tail->prev;
removeNode(node);
return node;
}
};
Golang:
type LRUCache struct {
size int
capacity int
cache map[int]*DLinkedNode
head, tail *DLinkedNode
}
type DLinkedNode struct {
key, value int
prev, next *DLinkedNode
}
func initDLinkedNode(key, value int) *DLinkedNode {
return &DLinkedNode{
key: key,
value: value,
}
}
func Constructor(capacity int) LRUCache {
l := LRUCache{
cache: map[int]*DLinkedNode{},
head: initDLinkedNode(0, 0),
tail: initDLinkedNode(0, 0),
capacity: capacity,
}
l.head.next = l.tail
l.tail.prev = l.head
return l
}
func (this *LRUCache) Get(key int) int {
if _, ok := this.cache[key]; !ok {
return -1
}
node := this.cache[key]
this.moveToHead(node)
return node.value
}
func (this *LRUCache) Put(key int, value int) {
if _, ok := this.cache[key]; !ok {
node := initDLinkedNode(key, value)
this.cache[key] = node
this.addToHead(node)
this.size++
if this.size > this.capacity {
removed := this.removeTail()
delete(this.cache, removed.key)
this.size--
}
} else {
node := this.cache[key]
node.value = value
this.moveToHead(node)
}
}
func (this *LRUCache) addToHead(node *DLinkedNode) {
node.prev = this.head
node.next = this.head.next
this.head.next.prev = node
this.head.next = node
}
func (this *LRUCache) removeNode(node *DLinkedNode) {
node.prev.next = node.next
node.next.prev = node.prev
}
func (this *LRUCache) moveToHead(node *DLinkedNode) {
this.removeNode(node)
this.addToHead(node)
}
func (this *LRUCache) removeTail() *DLinkedNode {
node := this.tail.prev
this.removeNode(node)
return node
}
/**
* Your LRUCache object will be instantiated and called as such:
* obj := Constructor(capacity);
* param_1 := obj.Get(key);
* obj.Put(key,value);
*/
4. 数组中的第K个最大元素【中等】
题目链接:https://leetcode.cn/problems/kth-largest-element-in-an-array/
参考题解:https://leetcode.cn/problems/kth-largest-element-in-an-array/solution/partitionfen-er-zhi-zhi-you-xian-dui-lie-java-dai-/
4.1 题目描述
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。
示例1:
输入: [3,2,1,5,6,4], k = 2
输出: 5
示例2:
输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4
提示:
- 1 <= k <= nums.length <= 10^5
- -10^4 <= nums[i] <= 10^4
4.2 解题思路
最简单的思路就是先把数组从小到大排序,返回倒数第 k 个元素即为所求。而根据快速排序每次都会确定一个元素最终位置的这一特点,可以使求解更加快速,即如果当前确定的元素位置恰好就是倒数第 k 个,那么直接返回就可以了。本题的难点是实现快速排序,处理边界情况。
4.3 代码实现
C++:
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
srand(time(0));
int n = nums.size();
int target = n - k;
int left = 0;
int right = n - 1;
while (true) {
int pivot = partition(nums, left, right);
if (pivot == target) {
return nums[pivot];
}
else if (pivot < target) {
left = pivot + 1;
}
else {
right = pivot - 1;
}
}
}
int partition(vector<int>& nums, int left, int right) {
int tmp = rand() % (right - left + 1) + left;
swap(nums[tmp], nums[left]);
int pivot = nums[left];
int j = left;
for (int i = left + 1; i <= right; ++i) {
if (nums[i] <= pivot) {
++j;
swap(nums[i], nums[j]);
}
}
swap(nums[left], nums[j]);
return j;
}
};
Golang:
func findKthLargest(nums []int, k int) int {
n := len(nums)
return quickselect(nums, 0, n - 1, n - k)
}
func quickselect(nums []int, l, r, k int) int{
if (l == r){
return nums[k]
}
partition := nums[l]
i := l - 1
j := r + 1
for (i < j) {
for i++; nums[i]<partition; i++{}
for j--; nums[j]>partition; j--{}
if (i < j) {
nums[i], nums[j] = nums[j], nums[i]
}
}
if (k <= j){
return quickselect(nums, l, j, k)
} else {
return quickselect(nums, j + 1, r, k)
}
}
5. 三数之和【中等】
题目链接:https://leetcode.cn/problems/3sum/
参考题解:https://leetcode.cn/problems/3sum/solution/san-shu-zhi-he-by-leetcode-solution/
5.1 题目描述
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请
你返回所有和为 0 且不重复的三元组。
注意: 答案中不可以包含重复的三元组。
示例1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
提示:
- 3 <= nums.length <= 3000
- -10^5 <= nums[i] <= 10^5
5.2 解题思路
首先把数组从小到大排序,这样可以减少循环次数。要满足 a + b + c = 0,如果当前遍历的是 a ,那么只需要判断 b + c 是不是等于 -a 就行了,对于从左往右遍历的每一个 b ,从右往左遍历 c ,这样 b + c 会从大不断变小,直到小于等于 -a ,并且 b 增大的话, c 只能不断变小往左走,否则 b + c 还会变大。所以 b 和 c 可以放到同一个循环中遍历。
5.3 代码实现
C++:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(), nums.end());
int n = nums.size();
vector<vector<int>> ans;
for (int a = 0; a < n; ++a) {
if (a > 0 && nums[a] == nums[a - 1]) {
continue;
}
int target = -nums[a];
int c = n - 1;
for (int b = a + 1; b < n; ++b) {
if (b > a + 1 && nums[b] == nums[b - 1]) {
continue;
}
while (b < c && nums[b] + nums[c] > target) {
--c;
}
if (b == c) {
break;
}
if (nums[a] + nums[b] + nums[c] == 0) {
ans.push_back({nums[a], nums[b], nums[c]});
}
}
}
return ans;
}
};
Golang:
func threeSum(nums []int) [][]int {
sort.Ints(nums)
n := len(nums)
ans := make([][]int, 0)
for a := 0; a < n; a++ {
if a > 0 && nums[a - 1] == nums[a] {
continue
}
target := -nums[a]
c := n - 1
for b := a + 1; b < n; b++ {
if b > a + 1 && nums[b - 1] == nums[b] {
continue
}
for b < c && nums[b] + nums[c] > target {
c--
}
if b == c {
break
}
if nums[a] + nums[b] + nums[c] == 0 {
ans = append(ans, []int{nums[a], nums[b], nums[c]})
}
}
}
return ans
}