CodeTop-LeetCode-1-5
3. 无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串 的长度。
思路:
- 使用滑动窗口表示子串区间,要保证窗口中没有重复元素
- 借助一个map记录已经出现过的元素和其位置。初始化窗口边界,右边界向后遍历并记录达到的最长窗口长度,当遇到重复元素,则把左边界移动到重复元素上一次出现位置之后(即跳过旧的重复元素)
代码:
class Solution {
public int lengthOfLongestSubstring(String s) {
int n = s.length(), ans = 0;
Map<Character, Integer> map = new HashMap<>();
for(int l = 0, r = 0; r < n; r++){
char c = s.charAt(r);
if(map.containsKey(c)){
//因为l表示不重复的第一个元素,所以是当前重复元素加1
//且,这里需要使得l为满足窗口内不重复的最右侧值
//如果只取当前重复值的下一个位置
//可能这个重复元素是很早(左侧)的元素
//而现在的l已经因为其他元素重复在后边了
//所以要取下标最大即最右侧的值
l = Math.max(map.get(c) + 1, l);
}
ans = Math.max(ans, r - l + 1);
map.put(c, r);
}
return ans;
}
}
146. LRU缓存机制
请你设计并实现一个满足 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) 的平均时间复杂度运行。
思路:
- 当需要淘汰数据的时候,选择最久没有被使用的数据淘汰,刚刚使用的数据不要淘汰
- 要求get和put的复杂度为O(1)
- 通过map根据key定位,找到位置后,移动到双向链表的头部,就可以在O(1)时间完成
- 为什么用双向链表
- 因为将某个节点移动到链表头部或者将链表尾部节点删去,都要用到删除链表中某个节点这个操作。从map中得到节点后,如果要在链表里删除,如果是单链表,则需要知道链表的前一个节点(从头遍历需要O(n)),而双向链表可以快速删除元素。
- 注意添加虚拟节点辅助,哨兵节点
- 为什么要在链表节点里存储key和value,而不是仅仅只存储 value
- 因为删去最近最少使用的键值对时,要删除链表的尾节点并且要删除map中的key-value,而如果尾节点没有存储key,那么就找不到要在map中删除哪个key,因此节点必须存储key
代码:
class LRUCache {
//定义节点类
private static class Node{
int key, value;
Node pre, next;
Node(int k, int v){
key = k;
value = v;
}
}
//设定的缓存空间
private final int capacity;
//哨兵节点
private final Node dummy = new Node(0, 0);
//存放元素的map
private final Map<Integer, Node> keyToNode = new HashMap<>();
public LRUCache(int capacity) {
this.capacity = capacity;
dummy.pre = dummy;
dummy.next = dummy;
}
public int get(int key) {
//定义getNode方法,获取值并移动到链表头部
Node node = getNode(key);
//没找到返回-1
return node != null ? node.value : -1;
}
public void put(int key, int value) {
//
Node node = getNode(key);
//本来就有这个元素
if(node != null){
node.value = value; //更新值
return ;
}
//新元素
node = new Node(key, value);
keyToNode.put(key, node);
pushFront(node); //定义方法,放到最前
if(keyToNode.size() > capacity){
//找到尾部的元素
Node backNode = dummy.pre;
keyToNode.remove(backNode.key); //在map里删除
remove(backNode); // 在链表里删除
}
}
//获取对应的节点,同时把该节点放到链表头部
private Node getNode(int key){
//没有这个节点返回null
if(!keyToNode.containsKey(key)){
return null;
}
//找到节点
Node node = keyToNode.get(key);
remove(node);//从链表中移除
pushFront(node); //重新添加到链表头部
return node;
}
//从链表中移除节点
private void remove(Node node){
node.pre.next = node.next;
node.next.pre = node.pre;
}
//在链表头部添加一个节点
private void pushFront(Node node){
node.pre = dummy;
node.next = dummy.next;
dummy.next = node;
//这里不要忘了原来的头节点指向新节点
node.next.pre = node;
}
}
206. 反转链表
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
思路:
- 采用迭代的方法通过两个指针和一个临时指针操作节点
代码:
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre = null; //记录已经反转的点
ListNode cur = head; //记录当前要反转的点
ListNode nxt = null; //记录下一次反转的点
while(cur != null){
nxt = cur.next;
cur.next = pre;
pre = cur;
cur = nxt;
}
return pre;
}
}
215. 数组中的第K个最大元素
给定整数数组 nums 和整数 k,请返回数组中第 **k** 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。
思路:
- 借助快排的思想,每次选择一个元素,根据大小划分区间(这里实际可以分为:小、相等、大,三个区间)。根据划分的区间的长度,我们可以知道第k大的元素会出现在哪个区间中。再对这个目标区间递归处理,当第K大的元素判断为再相等区间时,就找到他的值了
代码:
class Solution {
public int findKthLargest(int[] nums, int k) {
List<Integer> numList = new ArrayList<>();
for(int num : nums){
numList.add(num);
}
return quickSelect(numList, k);
}
//快速选择
//使用两个列表版本
private int quickSelect(List<Integer> nums, int k){
//随机选择基准数
Random rand = new Random();
//Random.nextInt方法用于生成一个随机的整数值。
//这个值位于0(包含)和指定值n(不包含)之间的范围内
int pivot = nums.get(rand.nextInt(nums.size()));
//两个列表存放划分的元素
List<Integer> big = new ArrayList<>();
List<Integer> small = new ArrayList<>();
//每个元素和pivot的大小比较
for(int num : nums){
if(num > pivot) big.add(num);
else if(num < pivot) small.add(num);
}
//第k大的元素在big中,对big列表划分
if(k <= big.size())
return quickSelect(big, k);
//第k大的元素在small中,对small列表划分
if(nums.size() - small.size() < k)
return quickSelect(small, k - nums.size() + small.size());
//第k大的元素在相等的元素中中,返回
return pivot;
}
}
15. 三数之和
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组
思路:
- 先固定一个值,另外两个值的和通过双指针处理
- 参考两数之和,把数组排序,然后使用相向双指针,逐渐缩小范围
- 注意需要对重复元素去重,保证三元组不重复。由于排序后的相同元素挨在一起,直接跳过相同元素就行
- 额外的,可以根据固定的值和最大两个值或最小两个值的和,提前判断循环结果进行优化
代码:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
//数组有序,就可以使用相向双指针了
//参考两数之和,固定一个值,根据最大值和最小值加起来的结果判断,需要往哪边找
//排序
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
int n = nums.length;
//由于是三数之和,第一个数只需要遍历到倒数第三个就可以,所以n-2
for(int i = 0; i < n - 2; i++){
int x = nums[i];
//为了保证没有重复三元组,跳过重复的数字
if(i > 0 && x == nums[i - 1]) continue;
//如果最小的x和相邻两个最小的值的和已经超出0,则直接结束返回空
if(x + nums[i + 1] + nums[i + 2] > 0) break;
//如果最小的x和最后两个最大的值的和还不够0,则跳过这个x,检查下一个
if(x + nums[n - 1] + nums[n - 2] < 0) continue;
//更新另外两个相向指针
int j = i + 1;
int k = n - 1;
while(j < k){
int s = x + nums[j] + nums[k];
if(s > 0){
k--;
} else if (s < 0){
j++;
} else{
//和为0,找到一个三元组
res.add(List.of(x, nums[j], nums[k]));
//找到后更新指针,避免有相同元素导致元组重复
//跳过重复元素
j++;
while(j < k && nums[j] == nums[j - 1])
j++;
k--;
while(k > j && nums[k] == nums[k + 1])
k--;
}
}
}
return res;
}
}

浙公网安备 33010602011771号