目录
关联问题_leetcode395: 至少有 K 个重复字符的最长子串
关联问题_leetcode153:寻找旋转排序数组中的最小值
关联问题_leetcode154: 寻找旋转排序数组中的最小值 II
0、单例模式
双重校验锁的实例必须加volatile修饰。这是因为java在初始化一个对象时分为三步:
- 分配内存空间。
- 初始化实例对象。
- 将内存空间的地址赋值给对应的引用。
但是由于操作系统可以
对指令进行重排序
,所以上面的过程也可能会变成如下过程:
- 分配内存空间。
- 将内存空间的地址赋值给对应的引用。
- 初始化对象
如果是这个流程,多线程环境下就可能将一个未初始化的对象引用暴露出来,从而导致不可预料的结果。因此,为了防止这个过程的重排序,我们需要将变量设置为volatile类型的变量。关键字: volatile详解 | Java 全栈知识体系
/**
* 饿汉模式:类加载时就初始化实例
* 这种写法比较简单,就是在类装载的时候就完成实例化,避免了线程同步问题。
* 缺点:在类装载的时候就完成实例化,没有达到lazy loading的效果,
* 如果从始至终都未使用过这个实例,则会造成内存的浪费
*/
public class SingleInstance{
// 构造函数必须是 private,不允许外部访问
private SingleInstance(){}
private static final SingleInstance instance = new SingleInstance();
public static SingleInstance getInstance(){
return this.instance;
}
}
/**
* 懒汉模式:需要用的时候再创建
*/
public class SingleInstance{
// 构造函数必须是 private,不允许外部访问
private SingleInstance(){}
private static SingleInstance instance;
// 线程安全的同步写法,效率低
public static synchronized SingleInstance getInstance(){
if(instance == null) {
instance = new SingleInstance();
}
return instance;
}
}
/**
* 双重校验锁模式:相比于懒汉线程安全模式,此方式效率更高,初始化完成后获取实例无需加锁
*/
public class SingleInstance{
// 构造函数必须是 private,不允许外部访问
private SingleInstance(){}
private volatile static SingleInstance instance;
public static SingleInstance getInstance(){
// 第一次校验为空
if(instance == null) {
// 加锁
synchronized(SingleInstance.class) {
// 第二次校验为空
if(instance == null) {
instance = new SingleInstance();
}
}
}
return instance;
}
}
1、leetcode3: 最长无重复子串
题目描述: 给定一个字符串,求其中最长的无重复子串。
例1: “abcab” -> 3
例1: “abba” -> 2
public static int lengthOfLongestSubstring(String s) {
if(s == null || s.length() == 0) return 0;
Map<Character, Integer> map = new HashMap<>();
int max = 0, left = 0;
for(int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if(map.containsKey(ch)) {
// 加max防止"abba" 这种case,left值反转
left = Math.max(left, map.get(ch) + 1);
}
map.put(ch, i);
max = Math.max(max, i-left+1);
}
return max;
}
关联问题_leetcode395: 至少有 K 个重复字符的最长子串
题目描述:给你一个字符串
s
和一个整数k
,请你找出s
中的最长子串, 要求该子串中的每一字符出现次数都不少于k
。返回这一子串的长度。如果不存在这样的子字符串,则返回 0。
public int longestSubstring(String s, int k) {
if(s == null || s.length() == 0) return 0;
// 1、统计每个字符出现次数
Map<Character, Integer> timesMap = new HashMap<>();
for(int i = 0; i<s.length(); i++) {
char ch = s.charAt(i);
int times = timesMap.getOrDefault(ch, 0);
timesMap.put(ch, times+1);
}
// 2、遍历map,如果字符出现次数<k,去掉此字符,重新递归获取最大长度.
for(Character ch : timesMap.keySet()) {
int times = timesMap.get(ch);
if(times < k) {
String[] strs = s.split(String.valueOf(ch));
int max = 0;
for(String str : strs) {
int len = longestSubstring(str, k);
max = Math.max(max, len);
}
return max;
}
}
return s.length();
}
2、leetcode206: 反转链表
// 递归
public static ListNode reverse(ListNode head){
if(head == null || head.next == null) return head;
ListNode node = reverse(head.next);
head.next.next = head;
head.next = null;
return node;
}
// 迭代
public static ListNode reverse(ListNode head){
if(head == null || head.next == null) return head;
ListNode pre = null, next;
while(head != null) {
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
关联问题_leetcode25: K个一组翻转链表
题目描述:给你链表的头节点
head
,每k
个节点一组进行翻转,请你返回修改后的链表。
k
是一个正整数,它的值小于或等于链表的长度。如果节点总数不是k
的整数倍,那么请将最后剩余的节点保持原有顺序。你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。例如:输入:head = [1,2,3,4,5], k = 3 输出:[3,2,1,4,5]
public ListNode reverseKGroup(ListNode head, int k) {
if(head == null || head.next == null || k<=1) return head;
ListNode res = new ListNode(-1), tmp = res, current = head, start, next;
tmp.next = head;
while(current != null) {
start = current;
for(int i=0; i<k-1 && current != null; i++) {
current=current.next;
}
if(current == null) return res.next;
next = current.next;
current.next = null;
tmp.next = reverse(start);
start.next = next;
tmp = start;
current = next;
}
return res.next;
}
public ListNode reverse(ListNode head) {
if(head == null || head.next == null) return head;
ListNode node = reverse(head.next);
head.next.next = head;
head.next = null;
return node;
}
关联问题_leetcode92: 反转链表 II
题目描述:给你单链表的头指针
head
和两个整数left
和right
,其中left <= right
。请你反转从位置left
到位置right
的链表节点,返回 反转后的链表 。例如:输入:head = [1,2,3,4,5], left = 2, right = 4 输出:[1,4,3,2,5]
public ListNode reverseII(ListNode head, int left, int right) {
if(head == null
|| head.next == null
|| left < 0
|| right < 0
|| left >= right ) {
return head;
}
ListNode res = new ListNode(-1), tmp = res, current = head, start, next;
tmp.next = head;
// 找到反转的起始位置
for(int i=0; i<left-1 && current != null; i++) {
tmp = tmp.next;
current = current.next;
}
if(current == null) return head;
start = current;
// 找到反转的结束位置. 往后遍历 right-left步
for(int i=0; i<right-left && current != null; i++) {
current = current.next;
}
if(current == null) return head;
next = current.next;
current.next = null;
// 反转 & 拼接
tmp.next = reverse(start);
start.next = next;
return res.next;
}
public ListNode reverse(ListNode head){
if(head == null || head.next == null) return head;
ListNode node = reverse(head.next);
head.next.next = head;
head.next = null;
return node;
}
3、leetcode146: 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)
的平均时间复杂度运行例如:
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解题思路:LRU底层是通过双端链表实现的。双端连表节点存储了key和value,以及指向上一个节点或者下一个节点的指针。插入、更新或者查询的节点都移动到队列头部,检查连表长度,末尾淘汰最近最久未使用的节点。因为题目要求get和put需要在O(1) 复杂度完成,可以通过map存储每个key跟节点的映射。
class LRUCache {
private int capacity;
private Node head;
private Node tail;
private Map<Integer, Node> cache;
public LRUCache(int capacity) {
this.capacity = capacity;
cache = new HashMap<>(capacity);
this.head = new Node();
this.tail = new Node();
this.head.next = tail;
this.tail.pre = this.head;
}
public int get(int key) {
if(!cache.containsKey(key)) return -1;
Node node = cache.get(key);
moveToHead(node);
return node.val;
}
public void put(int key, int value) {
Node node = cache.getOrDefault(key, null);
if(node != null) {
node.val = value;
moveToHead(node);
return;
}
node = new Node(key, value);
addToHead(node);
cache.put(key, node);
if(cache.size() > this.capacity) {
Node deleteNode = deleteLast();
cache.remove(deleteNode.key);
}
}
public void moveToHead(Node node) {
Node pre = node.pre;
Node next = node.next;
pre.next = next;
next.pre = pre;
addToHead(node);
}
public void addToHead(Node node) {
Node next = head.next;
head.next= node;
node.next = next;
next.pre = node;
node.pre = head;
}
public Node deleteLast(){
Node deleteNode = this.tail.pre;
Node pre = deleteNode.pre;
pre.next = this.tail;
this.tail.pre = pre;
return deleteNode;
}
/**
* 双端链表节点
*/
static class Node{
private int key;
private int val;
private Node pre;
private Node next;
public Node(){}
public Node(int key, int val){
this.key = key;
this.val = val;
this.pre = new Node();
this.next = new Node();
}
}
}
4、leetcode215: 数组中的第K个最大元素
题目描述:给定整数数组
nums
和整数k
,请返回数组中第k
个最大的元素。请注意,你需要找的是数组排序后的第k
个最大的元素,而不是第k
个不同的元素。你必须设计并实现时间复杂度为O(n)
的算法解决此问题。例如:输入: [3,2,1,5,6,4], k = 2。 输出: 5
解题思路1:使用优先队列【底层是堆】。
解题思路2:使用堆【小顶堆,堆底层存的是最大的k个数,堆顶即为所求】。
// 优先队列
class Solution1 {
public int findKthLargest(int[] nums, int k) {
if(nums == null || nums.length < k) return -1;
PriorityQueue<Integer> queue = new PriorityQueue<Integer>(k, (o1,o2) -> {return o1-o2;});
for(int i = 0;i<k; i++) {
queue.add(nums[i]);
}
for(int i=k; i< nums.length; i++) {
if(nums[i] > queue.peek()) {
queue.poll();
queue.add(nums[i]);
}
}
return queue.peek();
}
}
// 小顶堆
class Solution2 {
public int findKthLargest(int[] nums, int k) {
if(nums == null || nums.length < k) return -1;
int[] heap = new int[k];
for(int i = 0;i<k; i++) {
heap[i] = nums[i];
}
for(int i=k; i>=0; i--) {
adjust(heap, i, k);
}
for(int i=k; i< nums.length; i++) {
if(nums[i] > heap[0]) {
heap[0] = nums[i];
adjust(heap, 0, k);
}
}
return heap[0];
}
public void adjust(int[] nums, int i, int length) {
int left = 2*i+1, right = 2*i+2, smallest = i;
if(left < length && nums[left] < nums[smallest]) {
smallest = left;
}
if(right < length && nums[right] < nums[smallest]) {
smallest = right;
}
if(smallest == i) return;
swap(nums, i, smallest);
adjust(nums, smallest, length);
}
public void swap(int[] nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
}
5、leetcode25: K个一组翻转链表
public ListNode reverseKGroup(ListNode head, int k) {
if(head == null || head.next == null || k<= 1) {
return head;
}
ListNode res = new ListNode(-1), tmp = res, current = head, start, next;
res.next = head;
while(current != null) {
start = current;
for(int i=0; i<k-1 && current != null; i++) {
current = current.next;
}
if(current == null) return res.next;
next = current.next;
current.next = null;
tmp.next = reverse(start);
start.next = next;
tmp = start;
current = next;
}
return res.next;
}
public ListNode reverse(ListNode head){
if(head == null || head.next == null) return head;
ListNode node = reverse(head.next);
head.next.next = head;
head.next = null;
return node;
}
6、leetcode15: 三数之和
题目描述:给你一个整数数组
nums
,判断是否存在三元组:[nums[i],nums[j],nums[k]],
满足i != j
、i != k
且j != k
,同时还满足nums[i] + nums[j] + nums[k] == 0
。请你返回所有和为0
且不重复的三元组。注意:答案中不可以包含重复的三元组。
解题思路:
- 将数组排序
- 从下标0开始遍历
- 双指针循环遍历三个数累加之和是否=0
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
if(nums == null || nums.length < 3) return res;
// 排序
Arrays.sort(nums);
for(int i=0; i < nums.length; i++) {
// 不能有重复的,相同的跳过
if(i>0 && nums[i] == nums[i-1]) continue;
// 都是大于0的数,肯定不为0
if(nums[i] > 0) break;
int left = i+1, right = nums.length-1;
while(left<right) {
int sum = nums[i] + nums[left] + nums[right];
if(sum == 0) {
res.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 不能有重复值
while(left+1 < right && nums[left] == nums[left+1]) left++;
left++;
// 不能有重复值
while(left < right-1 && nums[right] == nums[right-1]) right--;
right--;
}else if(sum > 0) {
right--;
}else {
left++;
}
}
}
return res;
}
关联问题_leetcode16: 最接近的三数之和
题目描述:给你一个长度为
n
的整数数组nums
和 一个目标值target
。请你从nums
中选出三个整数,使它们的和与target
最接近。返回这三个数的和。假定每组输入只存在恰好一个解解题思路:
- 将数组排序
- 从下标0开始遍历
- 每次记录sum与target 差值的绝对值
- 输出最小的差值对应的和
public int threeSumClosest(int[] nums, int target) {
if(nums == null || nums.length < 3) return -1;
Arrays.sort(nums);
int minDiff = Integer.MAX_VALUE, res = 0;
for(int i=0; i < nums.length; i++) {
if(i>0 && nums[i] == nums[i-1]) continue;
int left = i+1, right = nums.length-1;
while(left < right) {
int sum = nums[i] + nums[left] + nums[right];
int diff = Math.abs(sum - target);
minDiff = Math.min(minDiff, diff);
res = minDiff == diff ? sum : res;
if(sum == target) return target;
if(sum < target) {
left++;
}else{
right--;
}
}
}
return res;
}
7、leetcode53: 最大子数组和
题目描述:求最大子序列之和。
例如: [-2, 1, -3, 4, -1, 2, 1, -5, 4], 输出: 6 [4,-1,2,1]
解题思路:遍历累加数组中的元素,如果sum >= 0, 接着累加,否则sum = nums[i]
public static int getMaxSubSummary(int[] nums){
if(nums == null || nums.length == 0) return -1;
int res = Integer.MIN_VALUE, sum = 0;
for(int i=0; i<nums.length; i++) {
if(sum >= 0) {
sum += nums[i];
}else {
sum = nums[i];
}
res = Math.max(res, sum);
}
return res;
}
8、手撕快排和堆排
快排:每次找一个基准点(partition),将这个基准点位置的值搞对,然后分左右接着排序。
public static void quickSort(int[] nums) {
if(nums == null || nums.length <= 1) return
doQuickSort(nums, 0, nums.length-1);
}
public static void doQuickSort(int[] nums, int left, int right) {
if(left < right) {
int partition = getPartition(nums, left, right);
doQuickSort(nums, left, partition-1);
doQuickSort(nums, partition+1, right);
}
}
public static int getpartition(int[] nums, int _left, int _right) {
int left = _left, right = _right;
// 选取基准点
int tmp = nums[left];
if(left != right) {
while(left < right) {
// 从右往左遍历,找到 < 基准点的值替换到left
while(left < right && nums[right] >= tmp) right--;
nums[left] = nums[right];
// 从左往右遍历,找到 > 基准点的值替换到right
while(left < right && nums[left] <= tmp) left++;
nums[right] = nums[left];
}
// 最后此下标即为基准点应该待的位置
nums[left] = tmp;
}
// left下标排序完毕
return left;
}
堆排:构建大顶堆,每次得到最大值,然后往数组后面交换,迭代即可
public static void heapSort(int[] nums) {
if(nums == null || nums.length <= 1) return;
// 建堆:大顶堆[最大值在最上面]
for(int i=nums.length-1; i>=0; i++) {
adjust(nums, i, nums.length);
}
// 遍历:每次把最大的值交换至队尾,剩余未排序部分继续调整
for(int i = nums.length-1; i>=0; i++) {
// 将本次获取最大值交换至i位置
swap(nums, 0, i);
// 继续从0开始,查找[0,i-1]之间最大的值
adjust(nums, 0, i);
}
}
public void adjust(int[] nums, int i, int length) {
// 获取左子节点和右子节点
int left = 2*i+1, right = 2*i+2, biggest = i;
// 获取最大的值索引
if(left < length && nums[left] < nums[biggest]) {
biggest = left;
}
if(right < length && nums[right] < nums[biggest]) {
biggest = right;
}
if(biggest == i) return;
// 交换
swap(nums, biggest, i);
// 递归
adjust(nums, biggest, length);
}
public void swap(int[] nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
9、leetcode21: 合并两个有序链表
public static ListNode mergeListNode(ListNode p, ListNode q) {
if(p == null) return q;
if(q == null) return p;
ListNode res = new ListNode(-1), tmp = res;
while(p != null && q != null) {
if(p.val < q.val) {
tmp.next = p;
p = p.next;
}else{
tmp.next = q;
q = q.next;
}
tmp = tmp.next;
}
return res.next;
}
关联问题_合并两个升序链表并去重1
题目描述: 合并两个升序链表,并去重,只保留一个重复元素。
例如:[1, 2, 2, 3, 4, 5, 5] + [0,2] 输出: [0,1,2,3,4,5]
public static ListNode mergeListNodeWithoutOneDuplicate(ListNode p, ListNode q){
ListNode res = new ListNode(-1), tmp = res;
while(p != null && q != null) {
// 去重
while(p.next != null && p.val == p.next.val) {
p = p.next;
}
while(q.next != null && q.val == q.next.val) {
q = q.next;
}
if(p.val < q.val) {
tmp.next = p;
p = p.next;
}else if (p.val > q.val) {
tmp.next = q;
q = q.next;
}else {
// 值相等,同时后移
tmp.next = p;
p = p.next;
q = q.next;
}
tmp = tmp.next;
}
if(p != null) {
while(p != null) {
while(p.next != null && p.val == p.next.val) {
p = p.next;
}
tmp.next = p;
p = p.next;
tmp = tmp.next;
}
}
if(q != null) {
while(q != null) {
while(q.next != null && q.val == q.next.val) {
q = q.next;
}
tmp.next = q;
q = q.next;
tmp = tmp.next;
}
}
return res.next;
}
关联问题_合并两个升序链表并去重2
题目描述: 合并两个升序链表,并去重,只保留一个重复元素。
例如:[1, 2, 2, 3, 4, 5, 5] + [0,2] 输出: [0,1,3,4]
private static ListNode mergeListNodeWithoutDuplicate(ListNode p, ListNode q) {
ListNode res = new ListNode(-1), tmp = res;
while (p != null && q != null) {
boolean pDuplicate = false, qDuplicate = false;
while (p.next != null && p.val == p.next.val) {
p = p.next;
pDuplicate = true;
}
while (q.next != null && q.val == q.next.val) {
q = q.next;
qDuplicate = true;
}
if (p.val == q.val) {
p = p.next;
q = q.next;
continue;
}
if (pDuplicate) {
p = p.next;
continue;
}
if (qDuplicate) {
q = q.next;
continue;
}
if (p.val < q.val) {
tmp.next = p;
p = p.next;
}else{
tmp.next = q;
q = q.next;
}
tmp = tmp.next;
}
if(p != null) {
tmp.next = deleteDuplicateNode(p);
}
if(q != null) {
tmp.next = deleteDuplicateNode(q);
}
return res.next;
}
public static ListNode deleteDuplicateNode(ListNode head) {
ListNode res = new ListNode(-1), tmp = res;
res.next = head;
while(tmp.next != null) {
if(tmp.next.next != null && tmp.next.val == tmp.next.next.val) {
int val = tmp.next.val;
while(tmp.next != null && tmp.next.val == val) {
tmp.next = tmp.next.next;
}
}else {
tmp = tmp.next;
}
}
return res.next;
}
10、leetcode1: 两数之和
题目描述:返回两数之和为target 的数组下标
解法思路:
- 如果数组是有序连表,可以直接通过双指针的方式求解。
- 数组没规定一定有序且必须返回下标,所以不能对数组排序。只能通过map求解。
public static int[] getTwoSum(int[] nums, int target) {
if(nums == null || nums.length <= 1) return new int[2];
Map<Integer, Integer> map = new HashMap<>();
for(int i = 0; i < nums.length; i++) {
int num = target - nums[i];
if(map.containsKey(num)) {
return new int[]{map.get(num), i};
}
map.put(nums[i], i);
}
return new int[2];
}
关联问题_leetcode39: 组合总和
题目描述:给你一个 无重复元素 的整数数组
candidates
和一个目标整数target
,找出candidates
中可以使数字和为目标数target
的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。candidates
中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。例:[1,1,2,1,4], target = 8,
输出:
[1,1,1,1,1,1,1,1]
[1,1,1,1,1,1,2]
[1,1,1,1,2,2]
[1,1,1,1,4]
[1,1,2,2,2]
[1,1,2,4]
[2,2,2,2]
[2,2,4]
[4,4]解题思路:
- 对数组排序
- dfs递归遍历。出口条件下标超限|累计和为target
- 递归的时候因为同一个元素可以重复使用,注意去重。且递归时传的idx参数需要注意
public List<List<Integer>> getDifferentCombine(int[] nums, int target) {
List<List<Integer>> res = new ArrayList<>();
if(nums == null || nums.length == 0) return res;
// 先排序
Arrays.sort(nums);
dfs(nums, 0, target, new ArrayList<>(), res);
return res;
}
public void dfs(int[] nums, int idx, int target, List<Integer> path, List<List<Integer>> res) {
// 递归退出条件1: 相加和为0
if(target == 0) {
res.add(new ArrayList<>(path));
return;
}
// 递归退出条件2: 下标超限
if(idx >= nums.length) {
return;
}
for(int i = idx; i < nums.length; i++) {
// 因为同一个数字可以重复使用,那么后面一样的直接去掉。
if(i>0 && nums[i] == nums[i-1]) {
continue;
}
int leftNum = target - nums[i];
// 相加和为负数,跳过
if(leftNum < 0) {
continue;
}
// 此处因为一个位置的元素可以重复使用,所以传入的是 i 不是 i+1
path.add(nums[i]);
dfs(nums, i, leftNum, path, res);
path.remove(path.size()-1);
}
}
关联问题_leetcode40: 组合总和 II
题目描述:给定一个候选人编号的集合
candidates
和一个目标数target
,找出candidates
中所有可以使数字和为target
的组合。
candidates
中的每个数字在每个组合中只能使用 一次 。注意:解集不能包含重复的组合。
例如:输入: [2,5,2,1,2], target = 5, 输出: [ [1,2,2], [5] ]
解题思路:
- 对数组排序
- dfs递归遍历。出口条件下标超限|累计和为target
- 递归的时候因为同一个元素不可以重复使用,递归时传的idx参数需要注意+1
- 过滤时 i > idx 而不是 i > 0.
public List<List<Integer>> combinationSum2(int[] nums, int target) {
List<List<Integer>> res = new ArrayList<>();
if(nums == null || nums.length == 0) return res;
Arrays.sort(nums);
dfs(nums, 0, target, new ArrayList<>(), res);
return res;
}
public void dfs(int[] nums, int idx, int target, List<Integer> path, List<List<Integer>> res) {
if(target == 0) {
res.add(new ArrayList<>(path));
return;
}
if(idx >= nums.length) return;
for(int i = idx; i<nums.length; i++) {
/** 区别1: 此处取i>idx 是因为 [1,1,2] 如果idx=1,那么这个1还是要取,不能过滤 */
if(i>idx && nums[i] == nums[i-1]) {
continue;
}
if(target - nums[i] < 0) {
continue;
}
/**区别2: 传入的下标+1 */
path.add(nums[i]);
dfs(nums, i+1, target - nums[i], new ArrayList<>(), res);
path.remove(path.size()-1);
}
}
11、leetcode5: 最长回文子串
题目描述:给你一个字符串
s
,找到s
中最长的 回文子串。例如:s = "babad" 输出:"bab" 解释:"aba" 同样是符合题意的答案。
解题思路:中心扩散算法
public String longestPalindrome(String s) {
if(s == null || s.length() == 0) return s;
String res = "";
for(int i =0; i< s.length(); i++) {
String str1 = getSubStr(s, i, i);
String str2 = getSubStr(s, i, i+1);
int maxLen = Math.max(res.length(), Math.max(str1.length(), str2.length()));
if(maxLen == str1.length()) res = str1;
if(maxLen == str2.length()) res = str2;
}
return res;
}
public String getSubStr(String s, int i, int j) {
while(i>=0 && j < s.length()) {
if(s.charAt(i) == s.charAt(j)) {
i--;
j++;
}else {
break;
}
}
return s.substring(i+1, j);
}
关联问题_leetcode647:回文子串
题目描述:给你一个字符串
s
,请你统计并返回这个字符串中 回文子串 的数目。回文字符串 是正着读和倒过来读一样的字符串。子字符串 是字符串中的由连续字符组成的一个序列。例1: s = "abc" 输出:3 解释:三个回文子串: "a", "b", "c"
例2: s = "aaa" 输出:6 解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"
解题思路:动态规划求解。定义dp[i] 表示以i-1处字符结尾的字符串的回文数。
- 默认值:dp[i] = dp[i-1] + 1;
- 遍历,0 <= j < i. 判断s.substring(j, i+1) 是否为回文串传,是的话dp[i]++;
public int countSubstrings(String s) {
if(s == null || s.length() == 0) return 0;
int[] dp = new int[s.length() + 1];
for(int i = 0; i < s.length(); i++) {
dp[i+1] = dp[i] + 1;
for(int j=0; j<i; j++) {
String substr = s.substring(j, i+1);
if(isHuiWen(substr)) {
dp[i+1]++;
}
}
}
return dp[s.length()];
}
public boolean isHuiWen(String s) {
int l = 0, r = s.length()-1;
while(l<r) {
if(s.charAt(l) == s.charAt(r)) {
l++;
r--;
}else {
return false;
}
}
return true;
}
关联问题_leetcode516:最长回文子序列
题目描述: 给你一个字符串
s
,找出其中最长的回文子序列,并返回该序列的长度。子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
例1: s = "bbbab" 输出:4 解释:一个可能的最长回文子序列为 "bbbb" 。
例2: s = "cbbd" 输出:2 解释:一个可能的最长回文子序列为 "bb" 。
解题思路:动态规划。可以删除或者不删除。 定义 dp[i][j] 表示[i, j] 下标的子字符串最长回文子序列长度。
- 如果子字符串长度为1,即 i=j, 那dp[i][j] = 1;
- 如果子字符串长度为2,即 j=i+1, 如果字符相等,那dp[i][j] = 2, 否则dp[i][j] = 1;
- 如果子字符串长度为3,即 j=i+2, dp[i][j] = Max(dp[i][j-1], dp[i+1][j], dp[i+1][j-1] + ij字符相等 ? 2:0);
- 如果子字符串长度为4,即 j=i+3, dp[i][j] = Max(dp[i][j-1], dp[i+1][j], dp[i+1][j-1] + ij字符相等 ? 2:0);
- ................
- 最后返回dp[0][len-1];即为从0到 len-1 下标的子字符串最长回文序列长度。
public int longestPalindromeSubseq(String s) {
if(s == null || s.length() == 0) return 0;
int length = s.length();
int[][] dp = new int[length][length];
// 初始化1: 一个元素时,长度为1
for(int i=0; i < s.length(); i++) {
dp[i][i] = 1;
}
// 初始化2: 2个元素时,相等长度为2,否则为1
for(int i=0; i < s.length()-1; i++) {
dp[i][i+1] = s.charAt(i) == s.charAt(i+1) ? 2 : 1;
}
for(int len = 3; len <= length; len++) {
for(int i = 0; i< length; i++) {
// j 为结束的下标,最大值为length-1
int j = i + len - 1;
if(j >= length) {
break;
}
if(s.charAt(i) == s.charAt(j)) {
dp[i][j] = max(dp[i+1][j], dp[i][j-1], dp[i+1][j-1] + 2);
}else {
dp[i][j] = max(dp[i+1][j], dp[i][j-1], dp[i+1][j-1]);
}
}
}
return dp[0][length-1];
}
public int max(int a, int b, int c) {
return Math.max(a, Math.max(b, c));
}
12、leetcode102: 二叉树的层序遍历
队列解法
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if(root == null) return res;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()) {
int len = queue.size();
List<Integer> list = new ArrayList<>();
for(int i = 0; i<len; i++) {
TreeNode node = queue.poll();
list.add(node.val);
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
}
res.add(list);
}
return res;
}
递归解法
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if(root == null) return res;
solveByRecursion(res, root, 1);
return res;
}
public void solveByRecursion(List<List<Integer>> res, TreeNode root, int depth) {
if(res.size() == depth-1) {
res.add(new ArrayList<>());
}
res.get(depth-1).add(root.val);
if(root.left != null) {
solveByRecursion(res, root.left, depth+1);
}
if(root.right != null) {
solveByRecursion(res, root.right, depth+1);
}
}
关联问题_leetcode103:二叉树的Z字型遍历
解题思路1:同层序遍历,可以用两个栈实现。
解题思路1:同层序遍历,用一个队列实现。偶数层直接反转
// 两个栈
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if(root == null) return res;
Stack<TreeNode> stack1 = new Stack<>();
Stack<TreeNode> stack2 = new Stack<>();
stack1.add(root);
while(!stack1.isEmpty() || !stack2.isEmpty()) {
if(!stack1.isEmpty()) {
int size = stack1.size();
List<Integer> list = new ArrayList<>();
for(int i = 0; i<size; i++) {
TreeNode node = stack1.pop();
list.add(node.val);
if(node.left != null) stack2.add(node.left);
if(node.right != null) stack2.add(node.right);
}
res.add(list);
}
if(!stack2.isEmpty()) {
int size = stack2.size();
List<Integer> list = new ArrayList<>();
for(int i = 0; i<size; i++) {
TreeNode node = stack2.pop();
list.add(node.val);
if(node.right != null) stack1.add(node.right);
if(node.left != null) stack1.add(node.left);
}
res.add(list);
}
}
return res;
}
// 一个队列
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if(root == null) return res;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
int depth = 0;
while(!queue.isEmpty()) {
int size = queue.size();
List<Integer> tmp = new ArrayList<>();
for(int i=0; i<size; i++) {
TreeNode node = queue.poll();
tmp.add(node.val);
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
}
depth++;
// 偶数层直接反转
if(depth % 2 == 0) {
Collections.reverse(tmp);
}
res.add(tmp);
}
return res;
}
关联问题_leetcode124:二叉树中的最大路径和
题目描述: 路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。路径和 是路径中各节点值的总和。给定一个二叉树的根节点
root
,返回其 最大路径和,即所有路径上节点值之和的最大值。例1: root = [1,2,3] 输出:6 解释:最优路径是 2 -> 1 -> 3 ,路径和为 2 + 1 + 3 = 6
例2: root = [-10,9,20,null,null,15,7] 输出:42 解释:最优路径是 15 -> 20 -> 7 ,路径和为 15 + 20 + 7 = 42
例3: root=[2, -1] ,输出2,最优路径为根节点2。
解题思路:二叉树问题用递归求解
- 递归返回条件:节点为null,返回0
- 求左子树最大的路径和left。
- 求右子树最大的路径和right。
- 更新最大值为max(sum, left + right + root.val);
- 返回root.val + max(left, right);
private int maxSummary = Integer.MIN_VALUE;;
public int maxPathSum(TreeNode root) {
if (root == null) {
return 0;
}
dfsGetSum(root);
return maxSummary;
}
private int dfsGetSum(TreeNode root) {
if (root == null) {
return 0;
}
// 此题的最大路径和不一定包含叶子结点,如[2,-1] 只算2根节点。
int leftSum = Math.max(dfsGetSum(root.left), 0);
int rightSum = Math.max(dfsGetSum(root.right), 0);
maxSummary = Math.max(maxSummary, leftSum + rightSum + root.val);
return Math.max(leftSum + root.val, rightSum + root.val);
}
13、leetcode33:搜索旋转排序数组
题目描述:整数数组
nums
按升序排列,数组中的值 互不相同 。在传递给函数之前,nums
在预先未知的某个下标k
(0 <= k < nums.length
)上进行了 旋转,使数组变为[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]
(下标 从 0 开始 计数)。例如,[0,1,2,4,5,6,7]
在下标3
处经旋转后可能变为[4,5,6,7,0,1,2]
。给你 旋转后 的数组nums
和一个整数target
,如果nums
中存在这个目标值target
,则返回它的下标,否则返回-1
。你必须设计一个时间复杂度为O(log n)
的算法解决此问题。例1: nums = [4,5,6,7,0,1,2], target = 0 输出:4
例2: nums = [4,5,6,7,0,1,2], target = 3 输出:-1
public int search(int[] nums, int target) {
if (nums == null || nums.length == 0) {
return -1;
}
int left = 0, right = nums.length-1, mid;
while (left <= right) {
mid = (left + right) >> 1;
if (nums[mid] == target) {
return mid;
}else if (nums[0] <= nums[mid]) {
// mid左半边有序 & target在左半边[nums[0], nums[mid]]
if (nums[mid] >= target && target >=nums[0]) {
right = mid-1;
}else {
left = mid+1;
}
}else{
// mid右半边有序 & target在右半边[nums[mid], nums[length-1]]
if (nums[mid] <= target && target <= nums[nums.length-1]) {
left = mid+1;
}else {
right = mid-1;
}
}
}
return -1;
}
关联问题_leetcode153:寻找旋转排序数组中的最小值
题目描述:给你一个元素值 互不相同 的数组
nums
,它原来是一个升序排列的数组,并进行了旋转。请你找出并返回数组中的 最小元素 。你必须设计一个时间复杂度为O(log n)
的算法解决此问题。例1: nums = [3,4,5,1,2] 输出:1
例2: nums = [4,5,6,7,0,1,2] 输出:0
解法思路:二分查找法。
public int findMin(int[] nums) {
int l = 0, r = nums.length-1;
while (l < r) {
// 地板除,更靠近left
int mid = l + (r - l) / 2;
if (nums[mid] < nums[r]) {
r = mid;
} else {
l = mid + 1;
}
}
return nums[l];
}
关联问题_leetcode154: 寻找旋转排序数组中的最小值 II
题目描述:给你一个可能存在 重复 元素值的数组
nums
,它原来是一个升序排列的数组,并进行了旋转。请你找出并返回数组中的 最小元素 。你必须设计一个时间复杂度为O(log n)
的算法解决此问题。例1: nums = [2,2,2,0,1], 输出:0
解法思路:二分查找法。因为有重复元素,所以在判断时,如果元素相等,需要特殊处理
public int findMin(int[] nums) {
int l = 0, r = nums.length-1;
while (l < r) {
int mid = l + (r - l) / 2;
if (nums[mid] < nums[r]) {
r = mid;
} else if(nums[mid] > nums[r]) {
l = mid + 1;
} else {
// 无法判断哪边有序,r自减
r = r - 1;
}
}
return nums[l];
}
14、leetcode200: 岛屿数量
题目描述:
给你一个由
'1'
(陆地)和'0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。此外,你可以假设该网格的四条边均被水包围。示例 1:
grid = [ ["1","1","1","1","0"], ["1","1","0","1","0"], ["1","1","0","0","0"], ["0","0","0","0","0"] ]。 输出:1
public int numIslands(char[][] grid) {
if(grid == null) return 0;
int m = grid.length, n = grid[0].length, num = 0;
for(int i = 0; i<m; i++) {
for(int j = 0; j<n; j++){
if(grid[i][j] == '1') {
mark(grid, i, j, m, n);
num++;
}
}
}
return num;
}
public void mark(char[][] grid, int i, int j, int m, int n) {
if(!inArea(i, j, m,n )) return;
if(grid[i][j] != '1') {
return;
}
grid[i][j] = '2';
mark(grid, i+1, j, m, n);
mark(grid, i-1, j, m, n);
mark(grid, i, j+1, m, n);
mark(grid, i, j-1, m, n);
}
public boolean inArea(int i, int j, int m, int n) {
return i>=0 && i<m && j>= 0 && j<n;
}
关联问题_leetcode695:岛屿的最大面积
题目描述:
给定一个由
0
和1
组成的非空二维数组grid
,用来表示海洋岛屿地图。一个 岛屿 是由一些相邻的1
(代表土地) 构成的组合,这里的「相邻」要求两个1
必须在水平或者竖直方向上相邻。你可以假设grid
的四个边缘都被0
(代表水)包围着。找到给定的二维数组中最大的岛屿面积。如果没有岛屿,则返回面积为0
。
public int maxAreaOfIsland(int[][] grid) {
if(grid == null) return 0;
int m = grid.length, n = grid[0].length, max = 0;
for(int i=0; i<m; i++) {
for(int j=0; j<n; j++) {
if(grid[i][j] == 1) {
max = Math.max(max, markAndGetMaxSquare(grid, i, j, m, n));
}
}
}
return max;
}
public int markAndGetMaxSquare(int[][] grid, int i, int j, int m, int n) {
if(!inArea(i, j, m, n)) return 0;
if(grid[i][j] != 1) return 0;
grid[i][j] = 2;
return 1 + markAndGetMaxSquare(grid, i+1, j, m, n)
+ markAndGetMaxSquare(grid, i-1, j, m, n)
+ markAndGetMaxSquare(grid, i, j+1, m, n)
+ markAndGetMaxSquare(grid, i, j-1, m, n);
}
public boolean inArea(int i, int j, int m, int n) {
return i>=0 && i<m && j>=0 && j<n;
}
关联问题_leetcode463: 岛屿的周长
题目描述:
给定一个
row x col
的二维网格地图grid
,其中:grid[i][j] = 1
表示陆地,grid[i][j] = 0
表示水域。网格中的格子 水平和垂直 方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。计算这个岛屿的周长。
public int islandPerimeter(int[][] grid) {
if(grid == null) return 0;
int m = grid.length, n = grid[0].length, max = 0;
for(int i=0; i<m; i++){
for(int j=0; j<n; j++) {
if(grid[i][j] == 1) {
max = Math.max(max, markAndGetMaxSquare(grid, i, j, m, n));
}
}
}
return max;
}
public int markAndGetMaxSquare(int[][] grid, int i, int j, int m, int n) {
if(!inArea(i, j, m, n)) return 1;
if(grid[i][j] == 2) return 0;
if(grid[i][j] == 0) return 1;
grid[i][j] = 2;
return markAndGetMaxSquare(grid, i+1, j, m, n)
+ markAndGetMaxSquare(grid, i-1, j, m, n)
+ markAndGetMaxSquare(grid, i, j+1, m, n)
+ markAndGetMaxSquare(grid, i, j-1, m, n);
}
public boolean inArea(int i, int j, int m, int n) {
return i>=0 && i<m && j>=0 && j<n;
}